WPF Pen DrawingVisual - wpf

I am working with WPF DrawingVisual and Pen and encountered a problem.
When I draw a DrawingVisual with Pen, say, a Rectangle as follows:
Pen StrokePen = new Pen();
StrokePen.Brush = Brushes.SkyBlue;
StrokePen.Thickness = 6;
DrawingVisual dv = new DrawingVisual
DrawingContext dc = dv.RenderOpen();
dc.DrawingRectangle(......., StrokePen, ......);
dc.Close();
I found that the half of the Stroke cover the rectangle like the following:
Therefore, if the Thickness of the Pen is too large so that it even larger than the Rectangle, the Rectangle will disappear (The whole rectangle is covered by the Stroke).
Could I adjust some setting so that the Stroke (Pen) drawn on the rectangle will not cover the rectangle (only draw beyond the sides of the rectangle)
Thank you.

You could simply draw the Rectangle twice, first with a Pen, then with a Brush:
using (DrawingContext dc = dv.RenderOpen())
{
...
dc.DrawingRectangle(null, StrokePen, ...);
dc.DrawingRectangle(FillBrush, null, ...);
....
}

In wpf border of rectangle is its internal content so no there is no way to force it to be outside of rectangle. But you can adjust size of your rectangle to compensate for Pen.Thickness.

Related

How can I draw a Polyline onto an Image (WPF)

I've tried a few different approaches to this, but can't seem to get a combination that works.
Creating WPF app in C#, Visual Studio.
System.Windows.Shapes.Polyline works really nicely to draw into a Canvas in real-time, but I want to be able to draw in higher resolution onto a non-visual component that I can then render onto an Image.
If I create a Polyline on a Canvas that's visible in the UI, this works fine:
// Make rendertarget size of full page
RenderTargetBitmap rtb = new RenderTargetBitmap((int)wPage, (int)hPage, 96, 96, PixelFormats.Default);
// Render the polyline
rtb.Render(lineVirt);
// Apply to background image
imgBG.Source = rtb;
But if I create a Polyline on a Canvas that's not visible in the UI, then nothing renders to the image. This is probably fair enough. My guess is that the component recognises that it's not visible and therefore doesn't bother to render.
I've considered putting the Canvas somewhere in the UI buried under other controls, but that seems like a horrible kind of hack.
Essentially, all I need is a clean and fast way to draw a multi-point line of a specified width and color onto an Image. I thought that Polyline would work well, but only seems to work in a visible container.
What are my options?
You do not need a rendered Canvas or any other visible Panel at all.
Just use basic drawing primitives available at the Visual layer.
The DrawGeometry method below draws a Geometry onto a BitmapSource, using the bitmap's rendered size, i.e. the size that takes its DPI into account, and returns the resulting BitmapSource.
public static BitmapSource DrawGeometry(
BitmapSource source, Pen pen, Geometry geometry)
{
var visual = new DrawingVisual();
var rect = new Rect(0, 0, source.Width, source.Height);
using (var dc = visual.RenderOpen())
{
dc.DrawImage(source, rect);
dc.DrawGeometry(null, pen, geometry);
}
var target = new RenderTargetBitmap(
(int)rect.Width, (int)rect.Height, 96, 96, PixelFormats.Default);
target.Render(visual);
return target;
}
In order to draw in the bitmap's pixel units and hence ignore its DPI, modify the method like this:
var rect = new Rect(0, 0, source.PixelWidth, source.PixelHeight);
using (var dc = visual.RenderOpen())
{
dc.DrawRectangle(new ImageBrush(source), null, rect);
dc.DrawGeometry(null, pen, geometry);
}
The following method uses the above to draw a polyline as an IEnumerable<Point>.
public static BitmapSource DrawPolyline(
BitmapSource source, Pen pen, IEnumerable<Point> points)
{
var geometry = new PathGeometry();
if (points.Count() >= 2)
{
var figure = new PathFigure { StartPoint = points.First() };
figure.Segments.Add(new PolyLineSegment(points.Skip(1), true));
geometry.Figures.Add(figure);
}
return DrawGeometry(source, pen, geometry);
}
It would be used like
var source = new BitmapImage(new Uri(...));
var pen = new Pen
{
Brush = Brushes.Blue,
Thickness = 2,
};
var points = new List<Point>
{
new Point(100, 100),
new Point(1000, 100),
new Point(1000, 1000),
new Point(100, 1000),
new Point(100, 100),
};
image.Source = DrawPolyline(source, pen, points);
Your canvas needs a size, so someone or something has to Arrange it. That might already be enough to get it to render, but the only reliable way of rendering arbitrary visuals to a bitmap is to actually place them in the visual tree of a window that's displayed and thus laid out by WPF. You can then render to the bitmap in a deferred task at ContextIdle priority to ensure that layout is complete.

How to remove Custom Strokes added to a WPF InkCanvas DrawingContext knowing only the bounding rectangle?

So I have an InkCanvas to which I have added "custom strokes".
I am at a complete loss as to how to erase custom strokes added to the InkCanvas through its DrawingContext. (Google has been of no help:( ) Making the assumption that I have a bounding rectangle for the area on the InkCanvas I want to erase, how can this be done?
(I am quickly coming to the conclusion that once something is drawn on the DrawingContext it can not be removed -- only convered :( ).
The Custom Strokes are created in the standard way by overriding the DrawCore method of the Stroke, e.g.,
// Draw Rectangle
public class RectangleStroke : Stroke
{
// Constructor
public RectangleStroke(StylusPointCollection pts)
: base(pts)
{
StylusPoints = pts;
}
protected override void DrawCore(DrawingContext drawingContext, DrawingAttributes drawingAttributes)
{
if (drawingContext == null)
{
throw new ArgumentNullException("drawingContext");
}
if (null == drawingAttributes)
{
throw new ArgumentNullException("drawingAttributes");
}
DrawingAttributes originalDa = drawingAttributes.Clone();
SolidColorBrush brush = new SolidColorBrush(drawingAttributes.Color);
brush.Freeze();
Pen pen = new Pen(brush, 1);
StylusPoint stp = StylusPoints[0];
StylusPoint sp = StylusPoints[1];
drawingContext.DrawRectangle(brush, pen, new Rect(new Point(sp.X, sp.Y), new Point(stp.X, stp.Y)));
}
}
Similar methods results in a picture like:
How are custom strokes erased knowing only the bounding rectangle?
Thanks for any help or suggestions.
Ugh...What should have been obvious is that the custom strokes inherit from Stroke and even though a datacontext once written can not be unwritten, the InkCanvas can erase its strokes--including custom strokes--by changing the InkCanvasEditingMode to EraseByStroke. Hope this helps somebody.

VisualBrush size and stretch problems

I would like to export a grid (whit all his children) to a PNG.
The problem is that some of these children are outside of the grid.
Here is my code:
VisualBrush sourceBrush = new VisualBrush(MyGrid);
DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext drawingContext = drawingVisual.RenderOpen())
{
drawingContext.DrawRectangle(sourceBrush, null, new Rect(new Point(0, 0), new Point(Math.Floor(exportWidth), Math.Floor(exportHeight))));
drawingContext.Close();
}
RenderTargetBitmap renderTarget = new RenderTargetBitmap((int)Math.Floor(exportWidth), (int)Math.Floor(exportHeight), 96, 96, PixelFormats.Default);
renderTarget.Render(drawingVisual);
The resulting image is blurred if at least one of the children is outside the grid.
The exportHeight and exportWidth values are calculated upstream, relative to the position of the grid's children.
If all children are inside the grid, the picture is clear.
I think this is because of the VisualBrush original size that cannot be changed.
Do you know a way to fix it ?
EDIT :
I do not call renderTarget.Render(MyGrid); because it does not take in charge children who are outside the grid (Children whose top or left value is negative).
Have you tried?
MyGrid.ClipToBounds = true;

Is it possible to Brush a DrawingVisual?

I am working with DrawingVisual in WPF and want to make some Effect.
Is it possible to brush a DrawingVisual with any brushes? That means:
Suppose I have a DrawingVisual:
DrawingVisual myVisual = new DrawingVisual();
myVisual.RenderOpen().....
.....
....
....
No matter how I draw, I want to make the whole visual to be in a color brush at the end.
something like: drawingcontext.drawvisual(myVisual, Brushes.Red) ??
Thank you.
A DrawingVisual is not itself a drawing object that can be filled with a Brush or outlined with a Pen. Instead it is a container for Drawing objects (and for other visuals, since it is also a ContainerVisual).
If you want to "fill" a DrawingVisual with a "background" brush, you would have to draw an appropriate Drawing, e.g. a large enough filled rectangle.

ContentControl + RenderTargetBitmap + empty image

Im trying to create some chart images without ever displaying those charts on the screen. I'v been at this for quite a while and tried a lot of different things but nothing seems to work. The code works perfectly if I display the chart in a window first, but if I don't display it in a window, the bitmap is just white with a black border (no idea why).
I have tried adding the chart to a border before rendering and giving the border a green borderBrush. In the bitmap, I see the green borderBrush then the black border and white background but no chart. The Chart is not contained in a black boarder so I don't know where that is coming from.
I have tried adding the chart to a window without calling window.Show() and again I just get the black boarder and white background. However if I call window.Show() the bitmap contains the chart.
I have tried using a drawingVisual as explained here, same result.
Here is the code (not including adding the element to a border or window):
private static BitmapSource CreateElementScreenshot(FrameworkElement element, int dpi)
{
if (!element.IsMeasureValid)
{
Size size = new Size(element.Width, element.Height);
element.Measure(size);
element.Arrange(new Rect(size));
}
element.UpdateLayout();
var scale = dpi/96.0;
var renderTargetBitmap = new RenderTargetBitmap
(
(int)(scale * element.RenderSize.Width),(int)(scale * element.RenderSize.Height),dpi,dpi,PixelFormats.Default
);
// this is waiting for dispatcher to perform measure, arrange and render passes
element.Dispatcher.Invoke(((Action)(() => renderTargetBitmap.Render(element))), DispatcherPriority.Render);
return renderTargetBitmap;
}
Note: The chart is a ContentControl.
Is there anyway I can get the chart to render without displaying it in a window first?
Calling element.ApplyTemplate() did the trick.
If someone has similar problems with rendering RenderTargetBitmap (getting white / empty image) items that are in StackPanel you can temporary move them to Grid, then render and put it back in StackPanel
Grid grid = new System.Windows.Controls.Grid() { Background = Brushes.White, Width = iWidth, Height = iHeight };
Panel panel = plot.Parent as Panel;
if (panel != null)
{
panel.Children.Remove(plot);
grid.Children.Add(plot);
grid.Measure(new Size(iWidth, iHeight));
grid.Arrange(new Rect(new Size(iWidth, iHeight)));
}
plot.Measure(new Size(iWidth, iHeight));
plot.Arrange(new Rect(new Size(iWidth, iHeight)));
plot.ApplyTemplate();
plot.UpdateLayout();
grid.ApplyTemplate();
grid.UpdateLayout();
RenderTargetBitmap renderTargetBitmap = new RenderTargetBitmap(
iWidth,
iHeight,
96, 96, PixelFormats.Pbgra32);
renderTargetBitmap.Render(grid);
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(renderTargetBitmap));
MemoryStream memoryStream = new MemoryStream();
encoder.Save(memoryStream);
bitmap = new System.Drawing.Bitmap(memoryStream);
if (panel != null)
{
grid.Children.Remove(plot);
panel.Children.Add(plot);
}
plot.Measure(new Size(iWidthBefore, iHeightBefore));
plot.Arrange(new Rect(new Size(iWidthBefore, iHeightBefore)));
plot.UpdateLayout();
For me, calling element.Arrange() was the missing piece.

Resources