I am trying to build a simple graphics application in WPF C#. The purpose is to draw 10000*10000 rectangles of size 4 pixels each.
I have modified the OnRender method of the canvas to draw the rectangles. Drawings are performed for smaller number of rectangles (say 50*50 or 100*100 rectangles of 4 pixel each) but it is slowing down as I am increasing the no. of rectangles.
Following is my code:
protected override void OnRender(DrawingContext dc)
{
base.OnRender(dc);
FillCells(dc);
if (_ShowGrids)
{
DrawGrid(dc); // draw grid lines
}
}
void FillCells(DrawingContext dc)
{
int cellSize=4;
for (int i = 0; i < MaxRow; i++)
{
for (int j = 0; j < MaxColumn; j++)
{
dc.DrawRectangle(GetRectBrush(i,j), GetRectPen(i,j), new Rect(j * cellSize , i * cellSize , cellSize - 1, cellSize - 1));
}
}
}
The above code takes more than a minute to draw 1000*1000 rectangles.
Is there any method to make this process faster? Is there any other thing I can use in place of this?
Thanks.
The purpose is to draw 10000*10000
rectangles of size 4 pixels each.
Do NOT draw them. That simple. This would be 40k to 40k pixels.
Most will not be visible. So they must not bee drawn. Basically only draw those that are visible in the canvas. When resizing or scrolling you repaint anyway, then do the same - only draw those that are visible.
Virtualization is the key to performance here. Take things out of the drawing loop as early as possible. Stuff not visible per definition does not need to be drawn at all.
Next alternative would be not to use a canvas. Try a bitmap. Prepare it on a separate thread, then draw this one at once.
You should try StreamGeometry then.
http://msdn.microsoft.com/en-us/library/system.windows.media.streamgeometry.aspx
For complex geometries that don’t need
to be modified after they are created,
you should consider using
StreamGeometry rather than
PathGeometry as a performance
optimization. StreamGeometry works
like PathGeometry, except that it can
only be filled via procedural code.
Its odd name refers to an
implementation detail: To use less
memory (and less of the CPU), its
PathFigures and PathSegments are
stored as a compact byte stream rather
than a graph of .NET objects.
Quoted from Adam Nathan's book WPF Unleashed.
You don't need to recreate the brush for each iteration of the loop, since they use the same color over and over:
SolidColorBrush blueBrush = new SolidColorBrush(Colors.Blue)
SolidColorPen bluePen = new SolidColorPen(blueBrush)
for (int i = 0; i < MaxRow; i++)
{
for (int j = 0; j < MaxColumn; j++)
{
dc.DrawRectangle(blueBrush, bluePen, 1), new Rect(j * cellSize , i * cellSize , cellSize - 1, cellSize - 1));
}
}
This may speed up the loop a bit.
One more tip on top of what everyone already said, make sure the pens and brushes are frozen - if you create the brush call Freeze before using it (brushes from the Brushes class (Brushes.White) are already frozen).
The bitmap approach might speed up more - BitmapSource has a Create method that takes raw data either as an array or a pointer to unsafe memory.
It should be a bit faster to set values in an array than drawing actual rectangles - however you have to checkout the pixelformats to set the individual pixels correctly.
Perhaps try overlaying the canvas with a VisualBrush.
To this visualBrush simply add the 4*4 rectangle and have it repeat in a tile mode. Alternatively you could just add the lines to it so that it doesnt overlap the edges of the rectangle... your choice :)
Your problem is in the creation of the brush... A test run indicated that this code
int limit = 10000 * 10000;
var converter = new BrushConverter();
for (int i = 0; i < limit; i++)
{
var blueBrush = converter.ConvertFromString("Blue") as Brush;
}
took 53 seconds to run. You are trying to create 100,000,000 brushes :) If it is patternable, use a patterned visual brush, if it is not patternable... perhapse look for another solution. The overhead of storing that many brushes in memory is in the Gigabytes
Related
I am fairly new in c# (3 weeks), and StackOverflow, though by searching did not find anything which would satisfy my answer in this page yet.
How can one make a x^2 function to be plotted in c# (obviously I am not interested only in x^2 but any function of my choice)
This should be plotted in grid as a user application. Before that I would need to gather some data from a binary file which user would be selecting himself and I assume to pass these points to arrays so that I could be able to plot a graph.
Issues which I am not familiar with.
How can I use arrays (if possible) to plot a graph with the least amount memory usage? Any links, reference to learn would be useful.
private void Pic1D_Click(object sender, RoutedEventArgs e)
{
Line myLine = new Line();
myLine.Stroke = System.Windows.Media.Brushes.LightSteelBlue;
myLine.X1 = 20;
myLine.Y1 = 20;
for (int i=0; i<=8; i++)
{
myLine.X2 = i+20;
myLine.Y2 = i*i+20;
myLine.VerticalAlignment = VerticalAlignment.Center;
myLine.StrokeThickness = 2;
FunctionGrid.Children.Add(myLine);
myLine.X2 = myLine.X1;
myLine.Y2 = myLine.Y1;
}
}
Another issue I am trying to research: Is it possible to plot the above graph by using binary inputs in arrays? Mainly the x value would be represented in binary as well the y would be represent in binary. Is there a function, class which I could use in order to do this? I know how to convert this in binary content, though the file itself is a raw file. Ideally in the end I would want to use the below read in file to plot the function above in 2 D.
if (NewDialogx.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
xPath.Text = NewDialogx.FileName;
}
byte[] fileBytes = File.ReadAllBytes(#xPath.Text);
StringBuilder sb = new StringBuilder();
foreach (byte b in fileBytes)
{
count++;
if (count > 4096)
{
sb.Append(Convert.ToString(b, 2).PadLeft(1, '!'));
// GraphPlot[count-512,0,0] = Convert.ToString(b, 2).PadLeft(8, '0');
}
}
File.WriteAllText(#"C:\Users\raiti\Desktop\NEW1", sb.ToString());
The below is how I tried to do this. I get this exception handling error:
System.ArgumentException: 'Specified Visual is already a child of another Visual or the root of a CompositionTarget.'
The issue is that I do not want to create this as list since this seems very impractical (though please do correct me) if I will need to plot around 10000+ data lines, and might take some space.
I hope I have been specific enough on this :).
I was going to put this in comments but there's way too much and I have some code to show.
I wouldn't usually worry about memory usage much, in a wpf application. This is presumably going to be running on a desktop and even the weaker end computers nowadays can cope with a shed load of graphics.
You should at least consider graphing software. There are a bunch of free possibilities. I also wouldn't totally dismiss setting wpf aside and dumping the data to disk, charting in excel.
If this is sort of an academic exercise in that you only win if you use the least memory then I'd have to try the options and look at how much memory they use.
The most efficient way to do graphics is supposed to be the various draw... options. You could draw lines into a picture. Although a bitmap has a fair bit of memory, you're potentially saving if you have many lines because the bitmap size won't go up whilst other approaches would use more memory.
You'd draw many lines using drawline, between each of the points.
https://msdn.microsoft.com/en-us/library/system.windows.media.drawingcontext(v=vs.110).aspx
https://msdn.microsoft.com/en-us/library/ms606810(v=vs.110).aspx
The following code (which I just happen to have to hand ) draws a set of lines on an image used as the overlay on the map here:
public static async Task<BitmapSource> GetGridImageAsync(FrameworkElement fe)
{
Matrix m = PresentationSource.FromVisual(fe)
.CompositionTarget.TransformToDevice;
double dpiFactor = 1 / m.M11;
return await Task.Run(() =>
{
Pen GreyPen = new Pen(Brushes.Gray, 1 * dpiFactor);
Pen GreyThickPen = new Pen(Brushes.Gray, 2 * dpiFactor);
GreyPen.Freeze();
int ix = 0;
int iy = 0;
int Width = 1155;
int Height = 805;
BitmapSource image = Visuals.CreateBitmap(
Width, Height, 96,
drawingContext =>
{
int count = 0;
while (ix <= Width)
{
if (count % 5 == 0)
{
drawingContext.DrawLine(
GreyThickPen, new Point(ix, 0), new Point(ix, Map.Height - 1));
}
else
{
drawingContext.DrawLine(
GreyPen, new Point(ix, 0), new Point(ix, Map.Height - 1));
}
ix += 35;
count++;
}
count = 0;
while (iy <= Width)
{
if (count % 5 == 0)
{
drawingContext.DrawLine(
GreyThickPen, new Point(0, iy), new Point(Map.Width -1, iy));
}
else
{
drawingContext.DrawLine(
GreyPen, new Point(0, iy), new Point(Map.Width - 1, iy));
}
iy += 35;
count++;
}
});
return image;
});
}
}
fe is the window passed in and I use that scaling so the lines are crisp. It gives a factor of .8 on my machine.
That low level approach might not really be necessary for your purpose, in which case you could use polylines ( as Clemens suggested ).
A common way to present numerous things using wpf is to use an itemscontrol and bind the itemssource. You then template the data you bind into ui objects ( a polyline each in this case ).
A polyline takes a pointcollection as it's Points property.
If your data is gathered and then doesn't change you can just bind that in a datatemplate like:
<Polyline Points="{Binding Points}" Stroke="Red" StrokeThickness="2" />
Where points is a public property presenting a pointcollection of some object you have for each row of your collection bound to itemssource.
If your lines are supposed to be curved then this is considerably more complicated. You can do Bezier curves using paths but you'd probably need to calculate based on your points.
At that point I'd be thinking again about using purpose built graphing software, personally.
2) Your array. You'd probably want to convert that to a pointcollection somewhere. That could be in a converter or in a viewmodel. MVVM ( and a viewmodel ) is the de facto standard pattern for wpf development.
3) Your error.
You get that when you try and use a piece of ui in two places.
https://social.technet.microsoft.com/wiki/contents/articles/29964.wpf-tips-only-one-parent.aspx
Sometimes it doesn't error but also doesn't give you the expected result.
I think I'd need to see more code to work out exactly why.
I want to make a chart in C# with custom elements. What I have:
What I want:
Elements marked by red circles need to be replaced by the image. My program code is very short, just some values for the chart. All settings for chart were set by the "Collection" in Chart section (as shown on first image).
This is a BoxPlot chart and it takes 6 y-values.
You can add them or let the chart calculate them.
Looks like you want to add several images to the various y-values..
Here is an example of how to do that by owner-drawing the Chart. (No, not the whole chart, just a little extra custom-drawing ;-)
It adds an image to each of the y-values; it should be easy to adapt to only those values you really want. And if you only want one you may even do away with the ImageList and pick the image from the resources; (although using an ImageList is a nice way, as long as you can live with the limitations of 256x256 maximum size and all images having the same size and color depth..)
You seem to want one of these only:
4 Average and mean
5 Median
private void chart_PostPaint(object sender, ChartPaintEventArgs e)
{
Series s1 = chart.Series[0];
ChartArea ca = chart.ChartAreas[0];
Axis ax = ca.AxisX;
Axis ay = ca.AxisY;
Graphics g = e.ChartGraphics.Graphics;
int iw = imageList1.ImageSize.Width / 2;
int ih = imageList1.ImageSize.Height / 2;
foreach (DataPoint dp in s1.Points)
{
int x = (int) ax.ValueToPixelPosition(dp.XValue);
for (int i = 0; i < 6; i++)
{
int y = (int) ay.ValueToPixelPosition(dp.YValues[i]);
g.DrawImage(imageList1.Images[i], x - iw, y - ih);
}
}
}
I suggest to use png files with transparency and an odd width so they look nice and centered. (I used randomly 16x16, which is not quite that nice ;-) - For this you need to set the ImageSize and the ColorDepth of the ImageList.
To further style the chart you may use these special properties
Custom attributes
BoxPlotPercentile, BoxPlotSeries, BoxPlotShowAverage,
BoxPlotShowMedian, BoxPlotShowUnusualValues, BoxPlotWhiskerPercentile,
DrawSideBySide, MaxPixelPointWidth, MinPixelPointWidth,
PixelPointDepth, PixelPointGapDepth, PixelPointWidth, PointWidth
Note that you need to set them all as strings, maybe like this:
s1.SetCustomProperty("someAttribute", "someValue");
I have WPF canvas. And I need to draw up to half million pixels with different color. I've tried to draw pixel-by-pixel, but it was incredibly slow. So I've decided to create Image and draw it. I don't know, if it's the best way how to do it, so if you know better way, tell me.
So my question is, how can I create and draw image to canvas? I've searched, but I wasn't able to find anything.
I have two dimensional array of colors and I need to draw them, probably via an image, so how can I do it?
Thanks, Soptik
EDIT: Now, I use this code, but it takes seconds to draw even 100*100 pixels.
for(int i = 0; i < w; i++)
{
for(int j = 0; j < h; j++)
{
Draw(i, j, Brushes.Aqua);
}
...
private void Draw(int x, int y, SolidColorBrush b)
{
Line l = new Line();
l.Stroke = b;
l.X1 = x;
l.Y1 = y;
l.X2 = x + 1;
l.Y2 = y + 1;
l.StrokeThickness = 1;
canvas.Children.Add(l);
}
Using your current method is not "bad." It might be slow due to the massive size of the 2d array you have, but looping through two for loops is normal for this process. Some potential solutions could be loading each row as a rect onto your Canvas to show the image being processed, but if that is not necessary than I would investigate how to handle the pixel data and possibly processing more than one at time.
This Question is similar to yours and might help
I need to graph rectangles of different heights and widths in a C# application. The rectangles may or may not overlap.
I thought the System.Windows.Forms.DataVisualization.Charting would have what I need, but every chart type I've explored wants data points composed of a single value in one dimension and multiple values in the other.
I've considered: Box, Bubble, and Range Bar.
It turns out that Richard Eriksson has the closest answer in that the Charting package doesn't contain what I needed. The solution I'm moving forward with is to use a Point chart to manage axes and whatnot, but overload the PostPaint event to effectively draw the rectangles I need on top. The Chart provides value-to-pixel (and vice versa) conversions.
Here is a minimal example that throws 100 squares of different colors and sizes randomly onto one Chart of ChartType Point with custom Marker Images.
You can modify to de-couple the datapoints from the colors, allow for any sizes or shapes etc..:
int count = 100;
int mSize = 60; // marker size
List<Color> colors = new List<Color>(); // a color list
for (int i = 0; i < count; i++)
colors.Add(Color.FromArgb(255, 255 - i * 2, (i*i) %256, i*2));
Random R = new Random(99);
for (int i = 0; i < count; i++) // create and store the marker images
{
int w = 10 + R.Next(50); // inner width of visible marker
int off = (mSize - w) / 2;
Bitmap bmp = new Bitmap(mSize, mSize);
using (Graphics G = Graphics.FromImage(bmp))
{
G.Clear(Color.Transparent);
G.FillRectangle(new SolidBrush(colors[i]), off, off, w, w);
chart5.Images.Add(new NamedImage("NI" + i, bmp));
}
}
for (int i = 0; i < count; i++) // now add a few points to random locations
{
int p = chart5.Series["S1"].Points.AddXY(R.Next(100), R.Next(100));
chart5.Series["S1"].Points[p].MarkerImage = "NI" + p;
}
Note that this is really just a quick one; in the Link to the original answer about a heat map I show how to resize the Markers along with the Chart. Here they will always stay the same size..:
I have lowered the Alpha of the colors for this image from 255 to 155, btw.
The sizes also stay fixed when zooming in on the Chart; see how nicely they drift apart, so you can see the space between them:
This may or may not be what you want, of course..
Note that I had disabled both Axes in the first images for nicer looks. For zooming I have turned them back on so I get the simple reset button..
Also note that posting the screenshots here introduces some level of resizing, which doesn't come from the chart!
I've been making a top-down shooter game in XNA that requires rectangular collision for the map.
The collision walls for a map is stored in a text file in the format of:rect[0,0,1024,8]
The values correspond to defining a rectangle (x, y, width, height).
I've been thinking that I could write a separate application that can illiterate through the data of the map image, find out the pixels that are black (or any color of the wall) and make rectangles there. Basically, this program will generate the rectangles required for the collision. Ideally, it would be pixel perfect, which would require something like a thousand rectangles each 1 pixel wide that covers all the walls.
Is there a possible way to detect which of these rectangles (or squares I should say) are adjacent to one another, then connect them into the a bigger (but still covering the same area) rectangle?
EG. Lets say I have a wall that is 10 by 2. The program would generate 20 different rectangles, each 1 pixel high. How would I efficiently detect that these rectangles are adjacent and automatically make a 10 by 2 rectangle covering the whole wall instead of having 20 different little pixel rectangles?
EDIT: I've worked out a solution that fits my purposes, for future reference, my code is below:
//map is a bitmap, horizontalCollisions and collisions are List<Rectangle>s
for (int y = 0; y < map.Height; y++) //loop through pixels
{
for (int x = 0; x < map.Width; x++)
{
if (map.GetPixel(x, y).Name == "ff000000") //wall color
{
int i = 1;
while (map.GetPixel(x + i, y).Name == "ff000000")
{
if (i != map.Width - x)
{
i++;
}
if (i == map.Width - x)
{
break;
}
}
Rectangle r = new Rectangle(x, y, i, 1);//create and add
x += i - 1;
horizontalCollisions.Add(r);
}
}
}
for (int j = 0; j < horizontalCollisions.Count; j++)
{
int i = 1;
Rectangle current = horizontalCollisions[j];
Rectangle r = new Rectangle(current.X, current.Y + 1, current.Width, 1);
while(horizontalCollisions.Contains(r))
{
i++;
horizontalCollisions.Remove(r);
r = new Rectangle(current.X, current.Y + i, current.Width, 1);
}
Rectangle add = new Rectangle(current.X, current.Y, current.Width, i);
collisions.Add(add);
}
//collisions now has all the rectangles
Basically, it will loop through the pixel data horizontally. When it encounters a wall pixel, it will stop the counter and (using a while loop) move the counter towards the right, one by one until it hits a non-wall pixel. Then, it will create a rectangle of that width, and continue on. After this process, there will be a big list of rectangles, each 1px tall. Basically, a bunch of horizontal lines. The next loop will run through the horizontal lines, and using the same process as above, it will find out of there are any rectangles with the same X value and the same Width value under it (y+1). This will keep incrementing until there are none, in which one big rectangle will be created, and the used rectangles are deleted from the List. The final resulting list contains all the rectangles that will make up all the black pixels on the image (pretty efficiently, I think).
Etiquette may suggest that I should comment this instead of add it as an answer, but I do not yet have that capability, so bear with me.
I'm afraid I am not able to translate this into code for you, but I can send you towards some academic papers that discuss algorithms that can do some of the things that you're asking.
Other time this questions has appeared:
Find the set of largest contiguous rectangles to cover multiple areas
Puzzle: Find largest rectangle (maximal rectangle problem)
Papers linked in those questions:
Fast Algorithms To Partition Simple Rectilinear Polygons
Polygon Decomposition
The Maximal Rectangle Problem
Hopefully these questions and papers can lead help you find the answer you're looking for, or at least scare you off towards finding another solution.