WPF DrawLine performance issue - c#

I need to draw about 12.000 lines in my project. When i was using WinForms, thanks for e.Graphics.DrawLine function of pictureBox, it was fine. But when i migrate the project to the WPF -which is i'm kinda new at- i decided to use canvas, and draw line as a children on it. Turns out it is the most insufficient method. But i just cannot succeed the other methods. Here is the function i've been using to draw a line on a canvas :
public void DrawLine(int x, int y1, int y2, System.Drawing.Color color)
{
Line top = new Line();
top.Stroke = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromArgb(color.A, color.R, color.G, color.B));
top.StrokeThickness = 5;
top.X1 = x;
top.Y1 = y1;
top.X2 = x;
top.Y2 = y2;
Canvas.SetTop(top, 0);
Canvas.SetLeft(top, 0);
scanCanvas.Children.Add(top);
}
When i tried to do a benchmark of my overall draw function by using stopwatch, it says there are just 300 ellapsed miliseconds, which seems fine. But what i see on the screen has a delay about 2-3 seconds. How can i draw lines on a canvas faster than this?

You should then rather use DrawingVisual Class.
DrawingVisual is a lightweight drawing class that is used to render shapes, images, or text. This class is considered lightweight because it does not provide layout, input, focus, or event handling, which improves its performance. For this reason, drawings are ideal for backgrounds and clip art.

Look at using StreamGeometry and freeze everything. Drawing in WPF is very, very slow.

Related

How to draw a lot of clickable rectangles in WPF?

I'm working on a basic mindmap program but I don't have a lot of experience with drawing with WPF. I want to be able to draw rectangles with text on them and i would like to be able to click on the rectangles to change the text for example.
As of now I have:
private void DrawSubject(int curve, double X, double Y, Brush clr)
{
Rectangle rect = new Rectangle();
rect.Width = 62;
rect.Height = 38;
rect.Fill = clr;
rect.Stroke = line;
rect.RadiusX = rect.RadiusY = curve;
Canvas.SetLeft(rect, X);
Canvas.SetTop(rect, Y);
mindmap.Children.Add(rect);
}
SolidColorBrush line = new SolidColorBrush(Color.FromArgb(255, 21, 26, 53));
minmap is the name of the canvas. I want to be able to draw a lot of these rectangles which present branches of the mindmap. However, when I drew 10,000 of these on random locations the process memory in the diagnostic tools went up by 100 MB, after it was done drawing all of them. I did this to sort of simulate a mindmap with 10,000 branches. So i was wondering if there might be a way to decrease the used memory for these rectangles?
Or is it better to use DrawingVisual and a grid.click event which checks if the clicked position matches the position of a rectangle by putting the coordinates of the rectangle in a List?
I would attempt the DrawingVisual method you described, if that proves costly in performance(I don't know how well DrawingVisual works) you could look into embedding OpenGL or DirectX into your application and rendering them via that.
But raytracing drawn visuals rather than making a Control for each is definitely the way to go for your scale.

WritableBitmap high speed drawing on Windows Phone

I'm experimenting with WritableBitmapEx on Windows Phone. I created a simple example, a simple box moving up and down.
There's a draw function which redraws the rectangle each frame:
int y = 0;
int dy = 15;
public void draw()
{
y += dy;
if (y > 500 || y < 0)
dy = -dy;
writeableBmp.Clear(System.Windows.Media.Colors.Black);
writeableBmp.FillRectangle(0, y, 100, y + 100, System.Windows.Media.Colors.Green);
}
and the Loaded event which creates the Writable bitmap and also calls draw() on each frame.
WriteableBitmap writeableBmp;
private async void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
{
writeableBmp = BitmapFactory.New((int)ContentPanel.ActualWidth, (int)ContentPanel.ActualHeight);
image.Source = writeableBmp;
writeableBmp.GetBitmapContext();
CompositionTarget.Rendering += CompositionTarget_Rendering;
}
private void CompositionTarget_Rendering(object sender, EventArgs e)
{
draw();
}
But this is giving my ~30FPS at most, so the animation is not smooth.
I know that there are some better ways of creating such animations in xaml (creating a rectangle object and animating using xaml animations for example), but there's a game (in another language) that redraws each frame this way, and my ultimate goal is porting that code to Windows Phone. So finding a way to redraw fast enough makes porting really easier.
So, is there a way to improve performance of this? or is there a better way to draw each frame manually, but fast enough (60fps)?
Try out Microsoft Win2D. You can get it using NuGet or their GitHub here: Microsoft Win2D GitHub. It's basically a wrapper over Direct2D and very simple to use.
Features (Copied from : https://github.com/Microsoft/Win2D/wiki/Features)
Easy-to-use Windows Runtime API
•Available from .NET and C++
•Supports Windows 10, Windows 8.1, and Windows Phone 8.1
Immediate mode 2D graphics rendering with GPU acceleration
•Implemented as a layer on top of Direct2D, DirectImage, and DirectWrite
•Interop to and from underlying types, so you can mix & match Win2D with native D2D
Bitmap graphics
•Load, save, and draw bitmap images
•Render to texture
•Use bitmaps as opacity masks
•Sprite batch API for efficiently drawing large numbers of bitmaps
•Use block compressed bitmap formats to save memory
•Load, save, and draw virtual bitmaps, which can be larger than the maximum GPU texture size and are automatically split into tiles
Vector graphics
•Draw primitive shapes (lines, rectangles, circles, etc.) or arbitrarily complex geometry
•Fill shapes using solid colors, image brushes, or linear and radial gradients
•Draw lines of any width with flexible stroke styles (dotted, dashed, etc.)
•High quality antialiasing
•Rich geometry manipulation (union, intersect, compute point on path, tessellate, etc.)
•Clip drawing to arbitrary geometric regions
•Capture drawing operations in command lists for later replay
•Rasterize ink strokes (from a stylus)
Powerful image processing effects
•Blurs
•Blends
•Color adjustments (brightness, contrast, exposure, highlights & shadows, etc.)
•Filters (convolve, edge detection, emboss, sharpen)
•Lighting
•Custom pixel shaders
•And many more...
Text
•Fully internationalized Unicode text rendering
•Text layouts can be drawn, measured, or hit-tested against
•Convert text outlines to geometry
•Enumerate fonts and query their metrics
•Draw or manipulate individual glyph runs to create custom text layouts
UI integration
•XAML CanvasControl make it easy to get up and running
•Can also create advanced things like owner-draw XAML controls
•XAML CanvasAnimatedControl provides Update/Draw game loop programming model
•XAML CanvasVirtualControl for drawing to very large virtualized surfaces
•Draw onto Windows.UI.Composition drawing surfaces and swapchains
•Can also draw directly to a CoreWindow
•Printing
Thanks to Chubosaurus Software who suggested Microsoft Win2D.
I'm gonna explain a little more about what I did in my case.
There's a control called CanvasAnimatedControl in Win2D, which is specially designed for this purpose.
After getting the package from NuGet and adding it to the page, you can use two events Draw and Update.
Update for the logic, and Draw is where you render the frame.
So, this is the code for the moving rectangle which described in the question:
private void Page_Loaded(object sender, RoutedEventArgs e)
{
cvs.Update += Cvs_Update;
cvs.Draw += Cvs_Draw;
}
int y = 0;
int dy = 15;
private void Cvs_Draw(Microsoft.Graphics.Canvas.UI.Xaml.ICanvasAnimatedControl sender, Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedDrawEventArgs args)
{
args.DrawingSession.Clear(Windows.UI.Colors.Blue);
args.DrawingSession.FillRectangle(new Rect(0, y, 100, 100), Windows.UI.Colors.Green);
}
private void Cvs_Update(Microsoft.Graphics.Canvas.UI.Xaml.ICanvasAnimatedControl sender, Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedUpdateEventArgs args)
{
y += dy;
if (y > 500 || y < 0)
dy = -dy;
}
This code runs great (~60fps) on my phone.
The only downside is, Win2D supports Windows Phone 8.1 and higher. So by using this library, you'll lose Windows Phone 8 compatibility.

Render Large Canvases in UserControl

I've been having trouble trying to implement this for a couple of days now. I've searched extensively on similar questions in regards to what I'm trying to do but I haven't come across a question that helps my issues directly.
Basically I'm rendering tiles onto a grid on my UserControl class. This is for my Tile Engine based world editor I'm developing. Here is a screenshot of an open world document and some tiles brushed on.
Initially, I was going to use a Bitmap in my control that would be the world's preview canvas. Using a brush tool for example, when you move your mouse and have the left button down, it sets the nearest tile beneath your cursor to the brush's tile, and paints it on the layer bitmap. The control's OnPaint method is overridden to where the layer bitmap is draw with respect to the paint event's clipping rectangle.
The issue with this method is that when dealing with large worlds, the bitmap will be extremely large. I need this application to be versatile with world sizes, and it's quite obvious there are performance issues when rendering large bitmaps onto the control each time it's invalidated.
Currently, I'm drawing the tiles onto the control directly in my control's overridden OnPaint event. This is great because it doesn't require a lot of memory. For example, a (1000, 1000) world at (20, 20) per tile (total canvas size is (20000, 20000)) runs at about 18mb of memory for the whole application. While not memory intensive, it's pretty processor intensive because every time the control is invalidated it iterates through every tile in the viewport. This produces a very annoying flicker.
What I want to accomplish is a way to meet in the middle as far as memory usage and performance. Essentially double buffer the world so that there isn't flickering when the control is redrawn (form resize, focus and blur, scrolling, etc). Take Photoshop for example - how does it render the open document when it overflows the container viewport?
For reference, here's my control's OnPaint override that is using the direct draw method mentioned above.
getRenderBounds returns a rectangle relative to PaintEventArgs.ClipRectangle that is used to render visible tiles, instead of looping through all the tiles in the world and checking if it's visible.
protected override void OnPaint(PaintEventArgs e)
{
WorldSettings settings = worldSettings();
Rectangle bounds = getRenderBounds(e.ClipRectangle),
drawLocation = new Rectangle(Point.Empty, settings.TileSize);
e.Graphics.InterpolationMode =
System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
e.Graphics.SmoothingMode =
System.Drawing.Drawing2D.SmoothingMode.None;
e.Graphics.PixelOffsetMode =
System.Drawing.Drawing2D.PixelOffsetMode.None;
e.Graphics.CompositingQuality =
System.Drawing.Drawing2D.CompositingQuality.HighSpeed;
for (int x = bounds.X; x < bounds.Width; x++)
{
for (int y = bounds.Y; y < bounds.Height; y++)
{
if (!inWorld(x, y))
continue;
Tile tile = getTile(x, y);
if (tile == null)
continue;
drawLocation.X = x * settings.TileSize.Width;
drawLocation.Y = y * settings.TileSize.Height;
e.Graphics.DrawImage(img,
drawLocation,
tileRectangle,
GraphicsUnit.Pixel);
}
}
}
Just comment if you need some more context from my code.
The trick is to not use a big bitmap for this at all. You only need a bitmap covering the visible area. Then you draw whatever is visible.
To achieve this you will need to maintain the data separately from the bitmap. This can be a simple array or an array/list with a simple class holding information for each block such as world position.
When your block is within the visible area then you draw it. You may or may not have to iterate through the whole array, but that isn't really a problem (you can also calculate the visible array on a separate thread). You can also make the function more intelligent by creating region indexes so you don't iterate all blocks.
To add a new block to the array, calculate it's canvas position to world coordinates, add it and then render the array again (or the area where the block is drawn).
This is how controls with scrollable areas are drawn by the system too.
Enable double-buffering will keep it clear and flicker-less.
In this case I would also use a panel with separate scroll bars and calculate the scroll-bars' relative position.

How to draw on a Window in WPF (best practice)?

I am trying to write a small interactive game-like application, where I need to have a Draw method that's gonna draw on screen, but can't figure out how to structure the method for WPF.
If this was Winforms, I could use:
public void Draw (Graphics g)
{
}
But for a WPF Window, what should I have on it in the xaml (currently only have a Grid), and what should this Draw method receive as an argument?
First I want to do it like this to get it working, then I can think about how to make it more WPF, etc. But now I am more interested in getting this to work.
Typically, you "draw" in WPF in a completely different manner.
In Windows Forms/GDI, the graphics API is an immediate mode graphics API. Each time the window is refreshed/invalidated, you explicitly draw the contents using Graphics.
In WPF, however, things work differently. You rarely ever directly draw - instead, it's a retained mode graphics API. You tell WPF where you want the objects, and it takes care of the drawing for you.
The best way to think of it is, in Windows Forms, you'd say "Draw a line from X1 to Y1. Then draw a line from X2 to Y2. Then ...". And you repeat this every time you need to "redraw" since the screen is invalidated.
In WPF, instead, you say "I want a line from X1 to Y1. I want a line from X2 to Y2." WPF then decides when and how to draw it for you.
This is done by placing the shapes on a Canvas, and then letting WPF do all of the hard work.
When there are just too many objects to be drawn very quickly (huge Visual Tree) another option would be to use a WriteableBitmap. Just use the Pixels property to set the pixels and/or use the Render method to draw UIElements.
I preffer to use OnRender method like in this example:
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
drawingContext.DrawRectangle(null, new Pen(Brushes.Black, 2), new Rect(0, 0, ActualWidth, Height));
}
To Implement a Draw loop type behavior in WPF you can use the CompositionTarget.Rendering event. This is raised once per frame when the WPF drawing system is painting frames.
As others have pointed out this is not very WPF friendly but it will work and can be used to get more immediate drawing behavior out of a WPF app.
In most cases you would use a single root canvas and update say the Canvas position of an element on the CompositionTarget.Rendering event.
For example to make a ellipse fly all over the screen do this:
In your XAML (For a Window that is 640 by 480 in size):
<Canvas x:Name="theCanvas">
<Ellipse x:Name="theEllipse" Height="10" Width="10" Fill="Black" />
</Canvas>
In your Code behind for the Window that the above XAML is in (Make sure to add a reference to System.Windows.Media in order to see the CompsitionTarget object :
public static Random rand = new Random();
public View()
{
InitializeComponent();
CompositionTarget.Rendering += CompositionTarget_Rendering;
}
void CompositionTarget_Rendering(object sender, System.EventArgs e)
{
double newLeft = rand.Next(0, 640);
double newTop = rand.Next(0, 480);
theEllipse.SetValue(Canvas.LeftProperty,newLeft);
theEllipse.SetValue(Canvas.TopProperty, newTop);
}
Yow should add a Canvas (or change the Grid for a Canvas) and then draw over it. Here is Microsoft tut on drawing over a canvas
Also, I don't know how related is this other question to yours, but you might want to check it out.

What is the easiest way to draw a cube in Windows Forms?

I am making a Windows Forms app in C# for homework that accepts the length of a cube. It then displays the surface area and volume of the cube. That's easy enough but it also needs to draw the cube.
What I'd like to know is the easiest way to draw the cube. I must do this with the Graphics class.
My thoughts on how to do it so far:
paper = myPicBox.CreateGraphics();
myPen = new Pen(Color.Black);
myPen.Width = 3;
paper.DrawRectangle(myPen, xCoord, yCoord, width, height);
paper.DrawLine(myPen, pointOne, pointTwo); // Then repeat this line for the four lines on the Z-axis
paper.DrawRectangle(myPen, xCoord, yCoord, width, height); // Where xCoord and yCoord have been changed to be placed at the end of the lines I've drawn
This is pretty bulky, so I was wondering if there was an easier or simpler way to achieve the same thing?
As mentioned, that's probably the best you're going to get with WinForms. The best you can do is encapsulate your functionality into its own method such that you can draw more than one cube at once. So your DrawCube() method might take in an origin, length, and Graphics object and then draw it. The CreateGraphics call would come before any calls to DrawCube.
Also, after you are done with your Graphics object, you should dispose of it by calling paper.Dispose() (see thispage) or stick it in a using block. Also, check out this website that explains when to use CreateGraphics (basically when you are drawing outside of a Paint event handler, which you are doing)
In WinForms what you have done is only your best bet, if you are searching for somthing as DrawCube() method then sorry you don't have it in .Net. Any thing you do will involve using those primitive types like line and rectangle.

Categories