WPF Image Generation using an N x N grid of images - wpf

I'm working on a personal project that creates an single image from a grid of images. It takes a while to generate the image and doesn't refresh everytime only once the code is done executing. How can the make the interface still functional (not locked up) when its generating the image.
So to start:
I have a N x N grid of identifiers, based on the identifier I draw a specific image at (x,y) with a given scaled height and width.
This image is regenerated each iteration and needs to be updated on the WPF. It is also bound to the ImageSource of the Image on the xaml side
My issue is 'How do I improve performance of generating this large image' and 'How do I refresh the image as many times as I need to (per generation).
for (int i = 0; i < numberOfIterations; i++)
{
// Do Some Work
UpdateImage();
}
...
BitmapImage imgFlower = new BitmapImage(new Uri(#"Images\Flower.bmp", UriKind.Relative));
BitmapImage imgPuppy = new BitmapImage(new Uri(#"Images\Puppy.bmp", UriKind.Relative));
ImageSource GeneratedImage{ get{ GenerateImage(); } set; }
...
void UpdateImage() { OnPropertyChanged("GeneratedImage"); }
...
ImageSource GenerateImage()
{
RenderTargetBitmap bmp = new RenderTargetBitmap(223, 223, 96, 96, PixelFormats.Pbgra32);
DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext drawingContext = drawingVisual.RenderOpen())
{
double scaleRatio = CalculateScaleRatio();
DrawGridOfImages(drawingContext, scaleRatio);
}
bmp.Render(drawingVisual);
return bmp;
}
...
DrawGridOfImages(...)
{
double x,y;
for (int r = 0; r < NumberOfRows; r++)
{
x = r * scaleRatio;
for (int c = 0; c < NumberOfColumns; c++)
{
y = c * scaleRatio;
switch (imageOccupancy[r, c])
{
case Flower: drawingContext.DrawImage(imgFlower, new Rect(x,y,scaleRatio,scaleRation));
case Puppy: drawingContext.DrawImage(imgPuppy, new Rect(x,y,scaleRatio,scaleRatio));
}
}
}
}

There are two ways. To first and most beneficial would be to improve the perceived performance, do this by generating the image on a worker thread and use events to update the image on the UI thread at key points so your users can see the progress.
To improve actual performance, if you are targeting and using multicore systems you can try parallel functions if your iterations can actually be performed in parallel. This will require some work and a different mindset but will help if you put the effort in. I'd recommend studying PLINQ to get started.

Related

WPF, rendering a million polygons, nVidia Quardro K2200

I need to display more than a million polygons on a usercontrol to display CAD inquiry. Because I need to be able to zoom in to view the polygons, they can't be drawn onto a bitmap. So in the OnRender override, I use StreamGeometry, StreamGeometryContext to generate polygon geometry. I was surprised how little WPF takes advantages of the powerful nVidia GPU. On the nVidia Control Panel, Workstation, Manage GPU Utilization, I can monitor how many % the GPU has been tapped. During rendering, it routinely uses less than 30% during the OnRender call. After the end of the OnRender, no GPU has been use and the CPU is still clunking away data in the background trying to display. After the polygons are drawn, it becomes impossible to scroll (the control is in a ScrollViewer), change opacity, etc. These operations do not tap into any of the GPU resources either. My only solution so far is set the usercontrol's CacheMode = new BitmapCache(8).
How can I let WPF uses the GPU in this case?
for (int j = 0; j < Data.Count; j++)
{
StreamGeometry geometry = new StreamGeometry();
geometry.FillRule = FillRule.Nonzero;
using (StreamGeometryContext context = geometry.Open())
{
...
{
Point startPoint = new Point(aPolygon[0].X, aPolygon[0].Y);
List<Point> points = new List<Point>();
//draw polygons
bool isPolygonViewAble = false;
if (viewingRect.Contains(startPoint)) isPolygonViewAble = true;
for (int i = 1; i < aPolygon.Count; i++) //foreach (CADGlobal.aPoint pt in polygon)
{
Point apoint = new Point(aPolygon[i].X, aPolygon[i].Y);
if (viewingRect.Contains(apoint)) isPolygonViewAble = true;
points.Add(apoint);
}
if (isPolygonViewAble)
{
context.BeginFigure(startPoint, true, true);
context.PolyLineTo(points, true, false);
}
}
}
geometry.Freeze();
dc.DrawGeometry(null, pen, geometry); //place null with a brush to fill

How to achieve smooth UI updates every 16 ms?

I am trying to create sort of a radar. Radar is VisualCollection that consists of 360 DrawingVisual's (which represent radar beams). Radar is placed on Viewbox.
class Radar : FrameworkElement
{
private VisualCollection visuals;
private Beam[] beams = new Beam[BEAM_POSITIONS_AMOUNT]; // all geometry calculation goes here
public Radar()
{
visuals = new VisualCollection(this);
for (int beamIndex = 0; beamIndex < BEAM_POSITIONS_AMOUNT; beamIndex++)
{
DrawingVisual dv = new DrawingVisual();
visuals.Add(dv);
using (DrawingContext dc = dv.RenderOpen())
{
dc.DrawGeometry(Brushes.Black, null, beams[beamIndex].Geometry);
}
}
DrawingVisual line = new DrawingVisual();
visuals.Add(line);
// DISCRETES_AMOUNT is about 500
this.Width = DISCRETES_AMOUNT * 2;
this.Height = DISCRETES_AMOUNT * 2;
}
public void Draw(int beamIndex, Brush brush)
{
using (DrawingContext dc = ((DrawingVisual)visuals[beamIndex]).RenderOpen())
{
dc.DrawGeometry(brush, null, beams[beamIndex].Geometry);
}
}
protected override Visual GetVisualChild(int index)
{
return visuals[index];
}
protected override int VisualChildrenCount
{
get { return visuals.Count; }
}
}
Each DrawingVisual has precalculated geometry for DrawingContext.DrawGeometry(brush, pen, geometry). Pen is null and brush is a LinearGradientBrush with about 500 GradientStops. The brush gets updated every few milliseconds, lets say 16 ms for this example. And that is what gets laggy. Here goes the overall logic.
In MainWindow() constructor I create the radar and start a background thread:
private Radar radar;
public MainWindow()
{
InitializeComponent();
radar = new Radar();
viewbox.Child = radar;
Thread t = new Thread(new ThreadStart(Run));
t.Start();
}
In Run() method there is an infinite loop, where random brush is generated, Dispatcher.Invoke() is called and a delay for 16 ms is set:
private int beamIndex = 0;
private Random r = new Random();
private const int turnsPerMinute = 20;
private static long delay = 60 / turnsPerMinute * 1000 / (360 / 2);
private long deltaDelay = delay;
public void Run()
{
int beginTime = Environment.TickCount;
while (true)
{
GradientStopCollection gsc = new GradientStopCollection(DISCRETES_AMOUNT);
for (int i = 1; i < Settings.DISCRETES_AMOUNT + 1; i++)
{
byte color = (byte)r.Next(255);
gsc.Add(new GradientStop(Color.FromArgb(255, 0, color, 0), (double)i / (double)DISCRETES_AMOUNT));
}
LinearGradientBrush lgb = new LinearGradientBrush(gsc);
lgb.StartPoint = Beam.GradientStarts[beamIndex];
lgb.EndPoint = Beam.GradientStops[beamIndex];
lgb.Freeze();
viewbox.Dispatcher.Invoke(new Action( () =>
{
radar.Draw(beamIndex, lgb);
}));
beamIndex++;
if (beamIndex >= BEAM_POSITIONS_AMOUNT)
{
beamIndex = 0;
}
while (Environment.TickCount - beginTime < delay) { }
delay += deltaDelay;
}
}
Every Invoke() call it performs one simple thing: dc.DrawGeometry(), which redraws the beam under current beamIndex. However, sometimes it seems, like before UI updates, radar.Draw() is called few times and instead of drawing 1 beam per 16 ms, it draws 2-4 beams per 32-64 ms. And it is disturbing. I really want to achieve smooth movement. I need one beam to get drawn per exact period of time. Not this random stuff. This is the list of what I have tried so far (nothing helped):
placing radar in Canvas;
using Task, BackgroundWorker, Timer, custom Microtimer.dll and setting different Thread Priorities;
using different ways of implementing delay: Environment.TickCount, DateTime.Now.Ticks, Stopwatch.ElapsedMilliseconds;
changing LinearGradientBrush to predefined SolidColorBrush;
using BeginInvoke() instead of Invoke() and changing Dispatcher Priorities;
using InvalidateVisuals() and ugly DoEvents();
using BitmapCache, WriteableBitmap and RenderTargetBitmap (using DrawingContext.DrawImage(bitmap);
working with 360 Polygon objects instead of 360 DrawingVisuals. This way I could avoid using Invoke() method. Polygon.FillProperty of each polygon was bound to ObservableCollection, and INotifyPropertyChanged was implemented. So simple line of code {brushCollection[beamIndex] = (new created and frozen brush)} led to polygon FillProperty update and UI was getting redrawn. But still no smooth movement;
probably there were few more little workarounds I could forget about.
What I did not try:
use tools to draw 3D (Viewport) to draw 2D radar;
...
So, this is it. I am begging for help.
EDIT: These lags are not about PC resources - without delay radar can do about 5 full circles per second (moving pretty fast). Most likely it is something about multithread/UI/Dispatcher or something else that I am yet to understand.
EDIT2: Attaching an .exe file so you could see what is actually going on: https://dl.dropboxusercontent.com/u/8761356/Radar.exe
EDIT3: DispatcherTimer(DispatcherPriority.Render) did not help aswell.
For smooth WPF animations you should make use of the
CompositionTarget.Rendering event.
No need for a thread or messing with the dispatcher. The event will automatically be fired before each new frame, similar to HTML's requestAnimationFrame().
In the event update your WPF scene and you're done!
There is a complete example available on MSDN.
You can check some graphics bottleneck using the WPF Performance Suite:
http://msdn.microsoft.com/es-es/library/aa969767(v=vs.110).aspx
Perforator is the tool that will show you performance issues. Maybe you are using a low performance VGA card?
while (Environment.TickCount - beginTime < delay) { }
delay += deltaDelay;
The sequence above blocks the thread. Use instead "await Task.Delay(...)" which doesn't block the thread like its counterpart Thread.Sleep(...).

Drawing multiple lines with DrawLines and DrawLine produces different result

I am trying to draw multiple lines on a winforms panel using it's graphics object in paint event. I am actually drawing a number of lines joining given points. So, first of all I did this,
private void panel1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawLines(new Pen(new SolidBrush(Color.Crimson), 3), PointFs.ToArray());
float width = 10;
float height = 10;
var circleBrush = new SolidBrush(Color.Crimson);
foreach (var point in PointFs)
{
float rectangleX = point.X - width / 2;
float rectangleY = point.Y - height / 2;
var r = new RectangleF(rectangleX, rectangleY, width, height);
e.Graphics.FillEllipse(circleBrush, r);
}
}
Which produces a result like the image below,
As you can see lines are drawn with having a little bit of extension at sharp turns, which is not expected. So, I changed the drawlines code to,
var pen = new Pen(new SolidBrush(Color.Crimson), 3);
for (int i = 1; i < PointFs.Count; i++)
{
e.Graphics.DrawLine(pen, PointFs[i - 1], PointFs[i]);
}
And now the drawing works fine.
Can anyone tell the difference between the two approaches?
I have just had the same problem (stumbled upon this question during my research), but I have now found the solution.
The problem is caused by the LineJoin property on the Pen used. This DevX page explains the different LineJoin types (see Figure 1 for illustrations). It seems that Miter is the default type, and that causes the "overshoot" when you have sharp angles.
I solved my problem by setting the LineJoin property to Bevel:
var pen = new Pen(new SolidBrush(Color.Crimson), 3);
pen.LineJoin = Drawing2D.LineJoin.Bevel;
Now DrawLines no longer overshoot the points.

DrawingContext.DrawLine performance problem

I was trying out different strategies for drawing a graph from the left edge of a control to the right edge. Until now we were using a Canvas with a polyline which performs OK, but could still use some improvement.
When I tried out DrawingContext.DrawLine I experienced incredibly bad performance, and I can't figure out why. This is the most condensed code I can come up with that demonstrates the problem:
public class TestControl : Control {
static Pen pen = new Pen(Brushes.Gray, 1.0);
static Random rnd = new Random();
protected override void OnRender(DrawingContext drawingContext) {
var previousPoint = new Point(0, 0);
for (int x = 4; x < this.ActualWidth; x += 4) {
var newPoint = new Point(x, rnd.Next((int)this.ActualHeight));
drawingContext.DrawLine(pen, previousPoint, newPoint);
previousPoint = newPoint;
}
}
}
And MainWindow.xaml just contains this:
<StackPanel>
<l:TestControl Height="16"/>
<!-- copy+paste the above line a few times -->
</StackPanel>
Now resize the window: depending on the number of TestControls in the StackPanel I experience a noticeable delay (10 controls) or a 30-second-total-standstill (100 controls) where I can't even hit the "Stop Debugger"-Button in VS...
I'm quite confused about this, obviously I am doing something wrong but since the code is so simple I don't see what that could be...
I am using .Net4 in case it matters.
You can gain performance by freezing the pen.
static TestControl()
{
pen.Freeze();
}
The most efficient way to draw a graph in WPF is to use DrawingVisual.
Charles Petzold wrote an excellent article explaining how to do it in MSDN Magazine:
Foundations: Writing More Efficient ItmesControls
The techniques work for displaying thousands of data points.
Ok, playing around with it a bit more, I found that freezing the pen had a huge impact. Now I create the pen in the constructor like this:
public TestControl() {
if (pen == null) {
pen = new Pen(Brushes.Gray, 1.0);
pen.Freeze();
}
}
The performance is now as I would expect it to be. I knew it had to be something simple...
Drawing in WPF becomes extremely slow if you use a pen with a dash style other than Solid (the default). This affects every draw method of DrawingContext that accepts a pen (DrawLine, DrawGeometry, etc.)
This question is really old but I found a way that improved the execution of my code which used DrawingContext.DrawLine aswell.
This was my code to draw a curve one hour ago:
DrawingVisual dv = new DrawingVisual();
DrawingContext dc = dv.RenderOpen();
foreach (SerieVM serieVm in _curve.Series) {
Pen seriePen = new Pen(serieVm.Stroke, 1.0);
Point lastDrawnPoint = new Point();
bool firstPoint = true;
foreach (CurveValuePointVM pointVm in serieVm.Points.Cast<CurveValuePointVM>()) {
if (pointVm.XValue < xMin || pointVm.XValue > xMax) continue;
double x = basePoint.X + (pointVm.XValue - xMin) * xSizePerValue;
double y = basePoint.Y - (pointVm.Value - yMin) * ySizePerValue;
Point coord = new Point(x, y);
if (firstPoint) {
firstPoint = false;
} else {
dc.DrawLine(seriePen, lastDrawnPoint, coord);
}
lastDrawnPoint = coord;
}
}
dc.Close();
Here is the code now:
DrawingVisual dv = new DrawingVisual();
DrawingContext dc = dv.RenderOpen();
foreach (SerieVM serieVm in _curve.Series) {
StreamGeometry g = new StreamGeometry();
StreamGeometryContext sgc = g.Open();
Pen seriePen = new Pen(serieVm.Stroke, 1.0);
bool firstPoint = true;
foreach (CurveValuePointVM pointVm in serieVm.Points.Cast<CurveValuePointVM>()) {
if (pointVm.XValue < xMin || pointVm.XValue > xMax) continue;
double x = basePoint.X + (pointVm.XValue - xMin) * xSizePerValue;
double y = basePoint.Y - (pointVm.Value - yMin) * ySizePerValue;
Point coord = new Point(x, y);
if (firstPoint) {
firstPoint = false;
sgc.BeginFigure(coord, false, false);
} else {
sgc.LineTo(coord, true, false);
}
}
sgc.Close();
dc.DrawGeometry(null, seriePen, g);
}
dc.Close();
The old code would take ~ 140 ms to plot two curves of 3000 points. The new one takes about 5 ms. Using StreamGeometry seems to be much more efficient than DrawingContext.Drawline.
Edit: I'm using the dotnet framework version 3.5
My guess is that the call to rnd.Next(...) is causing a lot of overhead each render. You can test it by providing a constant and then compare the speeds.
Do you really need to generate new coordinates each render?

Problems with rendering text as bitmaps using WPF

I am developing an application using WPF to dynamically render content, including text and images from WPF into jpg files. I am currently using the RenderTargetBitmap class to do the job. Everything works as expected but the quality of the rendered fonts is terrible. I understand that the RenderTargetBitmap doesn’t use the ClearType but a GrayScale antialias, which is kind of blury with small fonts. But I am using large fonts, larger than 30 pts, and the results are totally unacceptable. Is there some kind of workaround for this issue?
[Update]
The Code I am using is listed below. As expected it is called on each Rendering event of the CompositionTarget.
void CompositionTarget_Rendering(object sender, EventArgs e)
{
prefix = "";
if (counter < 10)
{
prefix = "000";
}
else if (counter < 100)
{
prefix = "00";
}
else if (counter < 1000)
{
prefix = "0";
}
Size size = new Size(MainCanvas.Width, MainCanvas.Height);
MainCanvas.Measure(size);
MainCanvas.Arrange(new Rect(size));
RenderTargetBitmap bmp = new RenderTargetBitmap(imgWidth, imgHeight, 96d, 96d, PixelFormats.Default);
bmp.Render(MainCanvas);
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.QualityLevel = 90;
encoder.Frames.Add(BitmapFrame.Create(bmp));
string file = basePath + prefix + counter.ToString() + "_testpic.jpg";
using (Stream stm = File.Create(file))
{
encoder.Save(stm);
}
counter++;
}
Here are some examples of the resulting images:
alt text http://www.randomnoise.org/temp/testpic_v1.jpg
alt text http://www.randomnoise.org/temp/testpic_v2.jpg
Thanks in advance.
Try this:
int height = (int)border.ActualHeight;
int width = (int)border.ActualWidth;
RenderTargetBitmap bmp = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
bmp.Render(border);
border being what you're trying to save as bitmap.
Ok, I finally found a solution. Gustavo you were on the right track. The problem was that the main container that I was trying to render as bitmap was being distorted by its parent container. The solution was to add the main container to a canvas, that doesn't have a layout engine that distorts its children. I still need to do some more experimenting but it looks very promising. Apparently RenderTargetBitmap doesn't like distorted fonts at all.

Resources