Bitmap cache on DrawingVisual has blurry parts

2019-07-31 13:53发布

问题:

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.

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.

回答1:

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.