How to draw graphics as efficiently as possible in WPF - c#

I am creating a tool which relies heavily on graph-node trees. The current implementation is done in Java and I'm porting it to a generic code-base on C#, so it can be used by various rendering implementations and also because I want to use the power of WPF for a user-friendly interface.
After browsing around for a day, I came across various methods to draw Vector-graphics through WPF.
This guy speaks about different layers within WPF developers can choose from. As I want to use WPF PURELY for his rendering at first, I want to work on the "Visual Layer".
I then came across things like:
DrawingVisual,
GeometryDrawing,
FrameworkElement / UIElement / Shapes
So, I'm a bit overwhelmed by all the different implementations that do eventually the same in totally different ways.
The Graph-Node library has been ported to C# already with all it's logic (including collision detection and dragging with mouse). As it is made with graphic-renderers in mind (like XNA, SlimDX, OpenTK, etc.), what would be the best way in terms of performance to implement a WPF renderer (as in, it will draw whatever the graph library tells it to draw?
Basically, the resulting WPF control acts as a canvas, but it has to be SUPER lightweight and not have any neat WPF features besides providing me a way to draw my circles, lines and other shapes :)
EDIT:
I basically want to know: What is the way to go? Do I extend Canvas as "Host" for my graphics and then add my custom implementation of a UIElement? Or can I have one class which can draw EVERYTHING (as in, one mega super ultra graphic). Much like overriding OnPaint in GDI or Paint-method in Java (which gives a Graphics object to do everything with).

I'd recommend reading Optimizing Performance: 2D Graphics and Imaging.
Basically, Drawing objects will be lighter weight than Shapes, in general. This is probably what you want to use.

Generally, better performance is obtained with lower-level services. In WPF, this means the Drawing family of objects. All you get are: Drawing, DrawingGroup, GeometryDrawing, GlyphRunDrawing, ImageDrawing, and VideoDrawing. However, they are sufficient for all needs. Using these types is very friendly with WPF because Drawing is the conceptual unit that WPF exchanges with your GPU accelerator, possibly retaining and managing it there if possible. This works because the Drawing is expressed in terms of portable vector drawing primitives.
Once you start re-architecting your app around Drawings however, you might need some interop with your higher-level code which is still based on UIElement, FrameworkElement, etc. One thing that I haven't found built-in to WPF is a simple way to wrap a Drawing as a FrameworkElement in the lowest-overhead way possible. DrawingVisual isn't a complete solution, because it only derives from Visual--meaning it still requires a hosting element.
The following class will host any WPF Drawing directly without using an intermediate DrawingVisual. I added support for FrameworkElement's Margin property (with no performance penalty if unused) but little else. Because of WPF's single rendering thread it's safe and easy to cache a single TranslateTransform object to implement the margin. I'd recommend that you supply only drawings which have been Frozen; in fact, in the version that I use, I have an assert to that effect in the constructor.
public class DrawingElement : FrameworkElement
{
static readonly TranslateTransform tt_cache = new TranslateTransform();
public DrawingElement(Drawing drawing)
{
this.drawing = drawing;
}
readonly Drawing drawing;
TranslateTransform get_transform()
{
if (Margin.Left == 0 && Margin.Top == 0)
return null;
tt_cache.X = Margin.Left;
tt_cache.Y = Margin.Top;
return tt_cache;
}
protected override Size MeasureOverride(Size _)
{
var sz = drawing.Bounds.Size;
return new Size
{
Width = sz.Width + Margin.Left + Margin.Right,
Height = sz.Height + Margin.Top + Margin.Bottom,
};
}
protected override void OnRender(DrawingContext dc)
{
var tt = get_transform();
if (tt != null)
dc.PushTransform(tt);
dc.DrawDrawing(drawing);
if (tt != null)
dc.Pop();
}
};
[edit:] This is also useful for inserting a WPF Drawing into the InlineUIContainer.Child property (i.e. using TextBlock.InlinesCollection to format the contents of the TextBlock more richly).

the DrawingVisual seems to be a valid choice:
The 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 or event handling, which improves
its performance. For this reason, drawings are ideal for backgrounds
and clip art.
source: Using DrawingVisual Objects
so this seems to be absolutely what you ask, a Canvas SUPER lightweight.

Related

Setting the transparency of a WPF ImageSource (equivalent to GDI Matrix33 = 0.5f)?

Does WPF have an equivalent to this?
ImageAttributes ia = new ImageAttributes();
ia.SetColorMatrix(new ColorMatrix { Matrix33 = 0.5f }, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
In other words, can I adjust the transparency of a WPF ImageSource (or any other drawing related class e.g. BitmapImage), or is GDI the best choice here?
I don't intend to draw the image onto a window, so I think that rules out using the Image class(?)
(My line of thought with trying to use WPF instead of GDI is primarily because I'm under the impression that with WPF I can have hardware acceleration, but from what I've seen so far, it seems that it's only applying to a very limited subset of image manipulation)
check out WriteableBitmapEx, which provides a set of extension methods for manipulating bitmaps in memory with WPF and Silverlight.
There is nothing in WPF per-se that will allow you to do want, but the WPF/Silverlight class WriteableBitmap provides a fast, low-level API to drawing/manipulating and is native to WPF.
WriteableBitmapEx then builds on this by providing extension methods to do GDI-like operations. There is a Convolute function which allows convolving an image with a matrix, so you could do the above. Note WB-Ex is a silverlight library but they also provide an unmaintained WPF version which although incomplete, can be extended to keep up to date with the Silverlight version.
Other than that I'd suggest rolling your own. If all you wish to do is modify the opacity to 0.5f then rather than convolve I'd suggest writing a specific function to do that based on the above examples
An image has an opacity field, which is a value between 1.0 (completely opaque) to 0.0 (invisible)
For example:
<Image
Height="107"
Margin="367,0,473,83"
Source="Images/4.png"
Stretch="Fill"
VerticalAlignment="Bottom" Opacity="0.5"/>
Following your comments I created a sample project which can modify the alpha channel quickly using WriteableBitmapEx as a basis and uploaded to my Company Blog. You'll have to forgive the formatting, its a new website and still has some glitches to be ironed out! The download link is on the page titled BitmapAlphaChannel
You're correct in saying WPF's hardware acceleration is only applied to a limited subset of features. For instance, all layout is done on the CPU, tessellation on the CPU, only the final rendering is done on the GPU. The result is WPF in my experience is slower than GDI+, certainly GDI. However it can be pushed in the right direction to do what you want, in the speed you want it to!

Graphics are too slow in my project

I am developing zoo simulator project. It contains three thing types to draw: a map, animal environments and the animals themselves. The map is too big to fit on screen, player needs to move screen to see other parts of it. I am using a timer to draw. On its tick, it calls Invalidate() for the form being drawing on. In ZooForm_Paint method, I first draw every thing in the map on mapBuffer Bitmap. Since mapBuffer is too big to fit on screen, I draw (on screen) the part of mapBuffer the player is where.
Unfortunately, it seems that drawing everything in the map (although it may not be viewed) on mapBuffer slows the game. Can I draw my evironments and animals without need to draw entire map first?
How?
My code:
public void DrawGame(Graphics g, ref Point locationOnMap)
{
this.drawBufferMap();
this.drawMapLocation(g, ref locationOnMap);
}
private void drawBufferMap()
{
Bitmap buffer = new Bitmap(this.map.Size.Width, this.map.Size.Height);
using (Graphics graphics = Graphics.FromImage(buffer))
{
graphics.DrawImageUnscaled(this.map.Picture, new Point()); // draw entire map
foreach (var item in this.zoo.Environments) // draw all env.
{
graphics.DrawImageUnscaled(item.Picture, item.Bounds.Location);
}
foreach (var item in this.zoo.ILocatables) // draw all ILocatables
{
graphics.DrawImageUnscaled(item.Picture, item.Location);
}
}
if (this.mapBuffer != null)
{
this.mapBuffer.Dispose();
}
this.mapBuffer = buffer;
}
private void drawMapLocation(Graphics g, ref Point location)
{
g.DrawImage(this.mapBuffer, new Rectangle(0, 0, viewSize.Width, viewSize.Height),
new Rectangle(location.X, location.Y, viewSize.Width, viewSize.Height), GraphicsUnit.Pixel);
}
I don't think you are going to get any easy solutions. I can offer a few tips and opinions:
You seem to be creating a new BitMap every time you paint the screen. This is definitely not a good idea, as large bitmaps are absolutely huge in terms of memory. What you probably want to do is create one when your game loads, and then simply clear it and repaint it at every frame. I think this is probably one of the bigger performance issues you have.
There are a number of optimisations you could make afterwards. E.g. you are "rendering" the image that you will end up painting to the screen on the user interface thread. If the rendering process takes long, this will be noticeable. Typically this work happens on a background thread, and then the UI thread just checks if it can repaint using the new image. (I am simplifying things greatly here).
For graphics intensive applications, WinForms is not a particularly good environment, as others have pointed out. You will not get any hardware acceleration at all. Moving to XNA is one option, but if your application is also quite rich in terms of standard WinForms screens and controls, this is probably not an easy option. Another suggested alternative might be WPF, where you might be able to get away with using transformations to move things around, which are hardware accelerated, and are not too dissimilar to a WinForms application (well, you don't need to implement your own buttons, etc).
Hope this helps a bit.
As Daniel pointed out: creating a new bitmap each time you need to draw your map will decrease performance. Reuse the same bitmap over and over instead.
Creating a bitmap larger that you need is also very bad for performance. If you need it to scroll around, then it's fine. But if you paint a new image each time anyway, then you should just create it exactly the same size you need. Then you can call Graphics.TranslateTransform to compensate for the new coordinates so you can leave your existion code unchanged.
This will make it possible for GDI+ to clip your graphics and simply just don't draw things outside your map bitmap - which will speed things up.

equivalent CreateGraphics in wpf

So, I've used winForms .CreateGraphics to draw a variety of different things, from lines to boxes to images. It was very snappy and responsive.
I am trying to learn WPF in C#
I found that WPF allows me to "add" rectangle objects to a canvas which will display them properly. HOWEVER, I am drawing hundreds of thousands of rectangles at times, and the draw rate can become exceedingly slow, and the UI becomes less snappy when I move even 1 of the rectangles.
Painting directly onto an element in winForms was not very fast, but it was consistent regardless of how much I painted.
Is there a similar solution to doing this in WPF?
I tried adding a linq to System.Drawing, which gave me a Graphics object, but none of the wpf elements i tried have the .CreateGraphics() method.
WPF uses a different model for graphics manipulation than WinForms.
With WinForms, you are able to directly edit the pixels on the screen. The concept of your rectangle is lost after the pixels are drawn. Drawing pixels is a very fast operation.
With WPF, you are not controlling the pixels on the screen. DirectDraw is. DirectDraw is a vector-based compositing engine. You do not draw pixels. You define vector shapes (or visuals). The concept of a shape, or a rectangle, is RETAINED, even after the image is rendered to the screen. When you add a new rectangle which overlaps the others, ALL OTHER RECTANGLES NEED TO BE REDRAWN. This is likely where your performance is slowing down. This does not happen when using WinForms.
You can improve the performance of WPF a bit by overriding OnRender. You can cut out the overhead of the Rectangle object and directly provide the visuals. However, you are still not drawing pixels to the screen. You are defining shapes that DirectDraw uses to render the image. In this regard, the OnRender name may be a bit misleading.
I am sure you can find plenty of tricks to improve performance of your application in WPF. There are ways to still paint pixels - but that is kinda defeating the point of WPF.
What are you doing that requires thousands of rectangles?
You would need to create a control that overrides OnRender and do your drawing in there. There isn't a way for you to draw onto another control, but a control can draw itself.
Also, keep in mind that WPF uses retained graphics, so if you change something you need to invalidate the visual as needed.
EDIT:
Something like:
public class MyControl : Control {
public MyControl() {
this.Rects = new ObservableCollection<Rect>();
// TODO: attach to CollectionChanged to know when to invalidate visual
}
public ObservableCollection<Rect> Rects { get; private set; }
protected override void OnRender(DrawingContext dc) {
SolidColorBrush mySolidColorBrush = new SolidColorBrush();
mySolidColorBrush.Color = Colors.LimeGreen;
Pen myPen = new Pen(Brushes.Blue, 10);
foreach (Rect rect in this.Rects)
dc.DrawRectangle(mySolidColorBrush, myPen, rect);
}
}
As was said, WPF uses a retained graphics methodology so your actually creating 100,000 Rectangle objects in memory and then drawing all of them. The slowdowns are probably due to garbage collection and general memory issues.
Aside from override the OnRender method, here's a couple of things you could look into though.
Drawing the rectangles to an image in a background thread using the GDI methods your familiar and then write the result to a WPF WriteableBitmap
Use the D3DImage and take advantage of hardware acceleration. This requires you to know the DirectX (or Direct2D) libraries. If your interested in this approach, I'd suggest looking into SlimDx.
The problem is most likeley not that WPF can't render 1000s of graphic objects, but that your creating and adding items too far up the WPF object hierachy. It does after all use the GPU for all the graphical grunt work.
You should add objects as close to the "Visual" class as possible, as soon as you start adding objects based on the latter "UIElement" you are asking WPF to track user clicks, hovers and so on for each object, not just draw it.

WPF: how to create complex user controls? (like GDI+)

For example, let's say I want to create something complex. like a zoommable/pannable graph. like google maps, or a stock market graph or something.
XAML and that whole hierarchy doesn't really work. What I'm trying to do is kind of more like back in the day with GDI+/Winforms. Where I could override paint
i.e., "protected override void OnPaint(PaintEventArgs e)" and then I'd draw whatever the heck I wanted. Where I'd do double buffering. Like draw to a buffer and blip it to the screen.
But how do I go about this in WPF?
A fundamental difference between WPF and GDI/GDI+/WinForms is that WPF uses retained mode rendering (as opposed to the direct rendering of GDI). In a nutshell, this means that the system (actually, the hardware) is taking care of the double buffering for you. Instead of procedurally drawing to the screen / buffer, you rather declaratively provide a tree of vector objects and leave all the rendering to WPF.
The vector objects come in different levels of complexity / abstraction - the most low-level ones you might ever want to deal with are Visuals. The Shapes (Ellipse, Rectangle etc.) mentioned by David are already higher-level objects which can also handle user interaction like hit-testing etc.
You can use any of the WPF shapes, like Ellipse, Rectangle and then using the Canvas class you can move them:
var rect = new Rectangle();
//...set width, height...
Canvas.SetTop(rect, 10);
Canvas.SetLeft(rect, 15);
This should get you started. Keep in mind that zooming, stretching content, traslating and rotations can all be achieved using math functions, but don't panic! WPF's got some cut things about it too:
var rotateTransform1 = new RotateTransform(45);
rect.RenderTransform = rotateTransform1;
If you really, really want it, you can derive your control from UIElement or FrameworkElement and override OnRender where you will get a DrawingContext object which provides methods for drawing shapes, text, images.
But if you want to work in the WPF's philosophy and spirit, probably 99% of the times you don't need to override OnRender. WPF offers a lot (and I really mean A LOT) of ways to develop new controls by styling, templating and if these two don't do the job, then subclassing the appropriate control in the WPF's controls hierarchy.
As gstercken very good pointed before, WPF is not WinForms, you must think in WPF in order to do a good work.

c# control location precision

I need more precision then integer based locations when puttng controls on a form.
It seems control.location only supports Point.
Is there a work around for this?
Point p = new Point(100, 200);
this.Location = p;// this works of course
PointF pF = new PointF(100.04f, 200.08f);
this.Location = pF;// this does not work of course because Location expects a Point not PointF
Is there some setting on the base form, or base control I can set to have more location precision?
You could use Windows Presentation Foundation (WPF) together with XAML (both work nicely within Visual Studio), which allows subpixel positioning of controls.
However, assuming that you are using Windows Forms and not WPF, why would you ever need to put a control on a non-integer (subpixel) location?
Although GDI+ is capable of floating point coordinate systems, Win32 (upon which Winforms is based) is not. I second the reccomendation to move to WPF which has a ubiquitous floating point coordinate system based on device independent virtual pixels.
No, control rendering in WinForms is all fundamentally pixel-based. There is a way you could achieve sub-pixel positioning (without using WPF), but it would be work on your part, and I'm really not sure why you would need this anyway.
The gist of the approach is to create a user control with a hidden instance of the control you're trying place funkily (sub-pixelly, maybe). You wire up all the user control's events so that they pass through to the hidden control, and after each event is passed you call the hidden control's DrawToBitmap method to get a snapshot of the control. You then use Graphics.DrawImage to copy the snapshot to the surface of the user control. DrawImage is not restricted to pixels, so you can offset the drawing by less than a pixel to achieve the precise positioning you're looking for.
Warning: please do not actually do this, as there is no reason for it when you can just use WPF. This would be a lot of work, since "passing the control's events through" is not as simple matter as it sounds. There's also a problem with rendering the focus correctly in this manner, as the invisible control cannot be given the focus (I'm not even going to tell you what the grisly hack solution is to that problem).
Update: It's worth revisiting that decision about WPF - it is ideal for what you're doing and would make your life much simpler. I have been generally underwhelmed by WPF, because I think that while it's very powerful it is essentially overpowered for the uses to which it is most often put (namely, boring-ass business apps). In your case, though, it provides a granularity that you actually require in your app.
If you're stuck in WinForms, however, your best approach is to write your own UserControl versions of the text-editing controls that your application requires. At its core, a TextBox is just a square that you draw a border around and some text on. The .Net Graphics methods for this (DrawRectangle and DrawString) can have the drawing coordinates specified in floating point.
There are tons of StackOverflow questions about owner-drawn user controls and GDI+ graphics.
1 point is 1 pixel and therefore the maximum resolution required.
This is the reason why 0,0 maps to the left corner, and 1024 maps to the right, in a resolution of 1024x768

Categories