1

Recently I switched to DrawingVisuals to increase the performance of our trending graphs (especially zooming and panning).

Here's the code I have:

blocksToBeRendered = (baseItem as AvgCurve).GetStreamGeometryBlocks(ActualWidth, ActualHeight, _minPoint.X, _maxPoint.X, FixTimeStep ? _timeStep : 0, IsMainChart);

Pen stroke = new Pen((baseItem as AvgCurve).LineBrush, 1);

foreach (GeometryGroup group in blocksToBeRendered)
{
    if (group.Children.Count != 0)
    {
        if (!cachedBlocks[baseItem].Any(x => x.Children[0] == group.Children[0]))
        {
            cachedBlocks[baseItem].Add(group);

            ImprovedDrawingVisual vis = new ImprovedDrawingVisual();

            BitmapCache cache = new BitmapCache() { SnapsToDevicePixels = true };
            vis.CacheMode = cache;

            using (DrawingContext context = vis.RenderOpen())
            {
                RenderOptions.SetEdgeMode(group, EdgeMode.Aliased);
                if (group.Children.Count > 0)
                {
                    context.DrawGeometry(null, stroke, group.Children[0]);
                }
            }

            _host.VisualCollection.Add(vis);
        }
    }
}

This is the ImprovedDrawingVisual:

public class ImprovedDrawingVisual: DrawingVisual
{
    public ImprovedDrawingVisual()
    {
        VisualEdgeMode = EdgeMode.Aliased;
        VisualBitmapScalingMode = BitmapScalingMode.NearestNeighbor;
    }
}

Now, the geometries do have Transforms, which might be important.

What happens is that the graphs are drawn nicely without bitmap caching (1 px lines), but when I turn on bitmap caching, parts of the graph go all blurry sometimes.

blurry lines

Does anyone know how I can fix this? I tried changing the RenderAtScale of the DrawingVisual or turning off the EdgeMode setting, but that doesn't help.

EDIT: Left out the brush fill geometry to avoid confusion since it's not relevant here.

Toon Casteele
  • 2,479
  • 15
  • 25
  • You are using `BitmapCache` (see [my](http://stackoverflow.com/q/25507861/1997232) question), which looks like a bitmap using jpg-compession. Perhaps you can implement own `BitmapCache` (which will not be a jpg)? Btw, to have a really high-performance graph I had to use `GDI+` in wpf. – Sinatr Oct 01 '14 at 09:42
  • Do you have an example of using GDI+, how would I go about that starting from my code example? – Toon Casteele Oct 01 '14 at 09:48

1 Answers1

2

If you decide to try GDI+ in wpf, then drawing will looks like this:

using GDI = System.Drawing;

private int _counter; // count redraws

protected override void OnRender(DrawingContext context)
{
    if (Figures != null && RenderSize.Height > 0 && RenderSize.Width > 0)
        using (var bitmap = new GDI.Bitmap((int)RenderSize.Width, (int)RenderSize.Height))
        {
            using (var graphics = GDI.Graphics.FromImage(bitmap))
                foreach (var figure in Figures)
                    figure.Render(this, graphics);
            // draw image
            var hbitmap = bitmap.GetHbitmap();
            var size = bitmap.Width * bitmap.Height * 4;
            GC.AddMemoryPressure(size);
            var image = Imaging.CreateBitmapSourceFromHBitmap(hbitmap, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
            image.Freeze();
            context.DrawImage(image, new Rect(RenderSize));
            DeleteObject(hbitmap);
            GC.RemoveMemoryPressure(size);
            // trigger garbage collecting
            if (_counter++ > 10)
                GC.Collect(3);
      }
}

This basically renders Graph in place (no deffered wpf rendering, well, only blitting of final image). You call InvalidateVisual() to redraw graph. Notice GC thingies, they are must (especially periodic garbage collection if graph gets redrawn frequently).

In my code Figures is a list of non-visuals (simple classes, implementing GDI rendering as well). And you have visual children. It's the problem, which I leave for you to solve. The benefit is very high performance.

Sinatr
  • 20,892
  • 15
  • 90
  • 319
  • I used to have geometries on top of a canvas which were redrawn every time I panned or zoomed. Is this more like that? – Toon Casteele Oct 01 '14 at 10:24
  • Nope, because visuals and `wpf` is slow. Do you set visuals in `xaml`? Or, saying otherwise, are graph figures (lines, orts, background, etc) **must** be a visual? If answer is "no", then replace them with non-visuals, which only knows how to render self into parent `Graph`. – Sinatr Oct 01 '14 at 10:27
  • I used to have paths, not visuals. These paths had geometries and were added onto the canvas. – Toon Casteele Oct 01 '14 at 10:28
  • Zooming/panning is performed during rendering. There are *local* coords, to example, point (100,100) of graph. And there are *mouse* coords. When rendering graph you convert local coords to mouse and then use GDI functions to draw lines or whatever. WPF transformation looks good at first, but you still have problem of calculating backwards (aka mouse coords into what point is that). Visuals solve hit-testing, but they are *slow*. – Sinatr Oct 01 '14 at 10:29
  • Thanks. I'll have a go at it with a few tests. It kind of sucks that I would have to throw away/heavily adapt all my geometry creation logic though :p – Toon Casteele Oct 01 '14 at 11:23
  • Okay. I'm implementing GDI right now. It's pretty awesome when you compare the performance to wpf drawing. Really amazing. Thanks. – Toon Casteele Oct 06 '14 at 07:41