Plot line graphs c#, array - c#

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.

Related

How to properly plot 3D surface with ZXYPositions in ilNumerics?

What I want to achieve?
I'm working on an evolutionary algorithm finding min/max of non-linear functions. I have fully functional WPF application, but there's one feature missing: 3D plots.
What is the problem?
To accomplish this I've started with free trial of ilNumerics which provide 3D data visualisation. It works completely fine with examples from documentation, but there's something what prevents me from plotting properly my own 3D graphs.
Visualising problem:
So, here is how it behaves at the moment
Those are graphs of non-linear function: x1^4+x2^4-0.62*x1^2-0.62*x2^2
Left side: Contour achieved with OxyPlot
Right side: 3D graph achieved with ilNumerics
As you can see, OxyPlot contour is completely fine and 3D graph which I'm trying to plot with exactly same data is not proper at all.
How actual (not working) solution is done?
I'm trying to visualise 3D surface using points in space. ILNumerics has class called Surface which object I have to create in order to plot my graph. It has following constructor:
public Surface(InArray<float> ZXYPositions, InArray<float> C = null, Tuple<float, float> colorsDataRange = null, Colormap colormap = null, object tag = null);
where as you can see ZXYPositions is what I actually have problem with. Before instantiating Surface object I'm creating an Array like this:
int m = 0;
for (int i = 0; i < p; ++i)
{
for (int j = 0; j < p; ++j)
{
sigma[m, 0] = (float)data[i, j];
sigma[m, 1] = (float)xy[0][i];
sigma[m, 2] = (float)xy[1][j];
m++;
}
}
where sigma[m, 0] = Z; sigma[m, 1] = X; sigma[m, 2] = Y;
And here's the problem. I cannot find any logical error in this approach.
Here is code responsible for creating object which I'm passing to ilNumerics plot panel:
var scene = new PlotCube(twoDMode: false) {
// add a surface
new Surface(sigma) {
// make thin transparent wireframes
Wireframe = { Color = Color.FromArgb(50, Color.LightGray) },
// choose a different colormap
Colormap = Colormaps.Jet,
}
};
Additionaly I want to say that sigma array is constructed properly, because I've printed out its values and they're definitely correct.
Plot only data points.
At the end I need to add, that when I'm not creating surface object and plot only data points it looks much more reasonable:
But sadly it's not what I'm looking for. I want to create a surface with this data.
Good News!
I found the answer. Oddly almost evereything was fine.. I missunderstood just one thing. When I'm passing ZXYPositions argument to surface it can actually expect only Z data from me to plot graph correctly.
What did I changed to make it work
Two first for loops now looks like that:
sigma = data;
As you can see they're no longer loops, because sigma now contains only "solution" coordinates (which are Z coords), so I need to just assign data array to sigma.
Second part, where I'm creating Surface now looks like this:
var B = ILMath.tosingle(sigma);
var scene = new PlotCube(twoDMode: false) {
// add a surface
new Surface(B) {
// make thin transparent wireframes
Wireframe = { Color = Color.FromArgb(50, Color.LightGray) },
// choose a different colormap
Colormap = Colormaps.Jet,
}
};
scene.Axes.XAxis.Max = (float)arguments[0].Maximum;
scene.Axes.XAxis.Min = (float)arguments[0].Minimum;
scene.Axes.YAxis.Max = (float)arguments[1].Maximum;
scene.Axes.YAxis.Min = (float)arguments[1].Minimum;
scene.First<PlotCube>().Rotation = Matrix4.Rotation(new Vector3(1f, 0.23f, 1), 0.7f);
Basically one thing which changed is scaling XY axes to proper values.
Final results
Here you have final results:

Combine BarChart and PointChart

i got a Little "Problem", i want to create a Chart looking like this:
So basically
Series 1 = Normal bar Chart. Color green if it Ends before the "time max" (series2) Series 2 = just a DataPoint / Marker on top of series 1 items.
I am struggling with this though...
my Code:
chart_TimeChart.Series.Clear();
string series_timeneeded = "Time Needed";
chart_TimeChart.Series.Add(series_timeneeded);
chart_TimeChart.Series[series_timeneeded]["PixelPointWidth"] = "5";
chart_TimeChart.ChartAreas[0].AxisY.ScrollBar.Size = 10;
chart_TimeChart.ChartAreas[0].AxisY.ScrollBar.ButtonStyle = ScrollBarButtonStyles.SmallScroll;
chart_TimeChart.ChartAreas[0].AxisY.ScrollBar.IsPositionedInside = true;
chart_TimeChart.ChartAreas[0].AxisY.ScrollBar.Enabled = true;
chart_TimeChart.Series[series_timeneeded].BorderWidth = 2;
chart_TimeChart.Series[series_timeneeded].ChartType = SeriesChartType.StackedBar;
chart_TimeChart.Series[series_timeneeded].YValueType = ChartValueType.Time;
chart_TimeChart.ChartAreas[0].AxisY.LabelStyle.Format = "HH:mm:ss";
chart_TimeChart.Series[series_timeneeded].XValueType = ChartValueType.String;
for (int i = 0; i < MaxNumber; i++)
{
chart_TimeChart.Series[series_timeneeded].Points.AddXY("item"+ " " + (i + 1).ToString(), DateTime.Now.Add(Timespans[i]));
}
chart_TimeChart.Series.Add(series_FinishTime);
chart_TimeChart.Series[series_FinishTime].ChartType = SeriesChartType.StackedBar;
chart_TimeChart.Series[series_FinishTime].BorderWidth = 0;
chart_TimeChart.Series[series_FinishTime].MarkerSize = 15;
chart_TimeChart.Series[series_FinishTime].MarkerStyle = MarkerStyle.Square;
chart_TimeChart.Series[series_FinishTime].MarkerColor = Color.Black;
chart_TimeChart.Series[series_FinishTime].YValueType = ChartValueType.DateTime;
chart_TimeChart.Series[series_FinishTime].XValueType = ChartValueType.String;
for (int i = 0; i < MaxNumber; i++)
{
DateTime YPosition = GetFinishTime(i);
chart_TimeChart.Series[series_FinishTime].Points.AddXY("item"+ " " +(i+1).ToString(), YPosition);
}
but this only Displays the 2nd series on top of the first one but the first one isnt visible anymore. The Maker of series 2 isnt shown but instead the bar is (eventhough i made borderwidth to 0). In my opinion/thinking i just have to make the "bar" of series 2 invisible and just Show the marker Points for series 2.
Any ideas?
Update:
string seriesname = Name+ i.ToString();
chart_TimeChart.Series.Add(seriesname);
chart_TimeChart.Series[seriesname].SetCustomProperty("DrawSideBySide", "false");
chart_TimeChart.Series[seriesname].SetCustomProperty("StackedGroupName", seriesname);
chart_TimeChart.Series[seriesname].ChartType = SeriesChartType.StackedBar; //Y and X are exchanged
chart_TimeChart.Series[seriesname].YValueType = ChartValueType.Time;
chart_TimeChart.ChartAreas[0].AxisY.LabelStyle.Format = "HH:mm:ss";
chart_TimeChart.Series[seriesname].XValueType = ChartValueType.String;
DateTime TimeNeeded = DateTime.Now.Add(List_AllLiniengroupsTimespans[k][i]);
DateTime TimeMax = GetFinishTime(k, i);
TimeSpan TimeDifference = TimeNeeded - TimeMax;
if (TimeNeeded > TimeMax) //All good
{
chart_TimeChart.Series[seriesname].Points.AddXY(seriesname, TimeNeeded); //Time till finish
chart_TimeChart.Series[seriesname].Points[0].Color = Color.Blue;
chart_TimeChart.Series[seriesname].Points[0].SetCustomProperty("StackedGroupName", seriesname);
chart_TimeChart.Series[seriesname].Points.AddXY(seriesname, TimeNeeded.Add(TimeDifference)); //time left
chart_TimeChart.Series[seriesname].Points[1].Color = Color.Red;
chart_TimeChart.Series[seriesname].Points[1].SetCustomProperty("StackedGroupName", seriesname);
}
else if (TimeMax > TimeNeeded) //wont make it in time
{
chart_TimeChart.Series[seriesname].Points.AddXY(seriesname, TimeNeeded); //time till still okay
chart_TimeChart.Series[seriesname].Points[0].Color = Color.Blue;
chart_TimeChart.Series[seriesname].Points[0].SetCustomProperty("StackedGroupName", seriesname);
chart_TimeChart.Series[seriesname].Points.AddXY(seriesname, TimeNeeded.Add(TimeDifference)); //Time that is too much
chart_TimeChart.Series[seriesname].Points[1].Color = Color.Green;
chart_TimeChart.Series[seriesname].Points[1].SetCustomProperty("StackedGroupName", seriesname);
}
else if (TimeMax == TimeNeeded) //fits exactly
{
chart_TimeChart.Series[seriesname].Points.AddXY(seriesname, TimeNeeded);
chart_TimeChart.Series[seriesname].Points[0].Color = Color.DarkOrange;
chart_TimeChart.Series[seriesname].Points[0].SetCustomProperty("StackedGroupName", seriesname);
}
the Code will be displayed as:
but i want it to look like this:
!! See the update below !!
If you really want to create a StackedBar chart, your chart has two issues:
If you want to stack datapoints they need to have meaningful x-values; without them how can it know what to stack on each other?
You add strings, which look fine but simply don't work. That is because the DataPoint.XValue field is double and when you add string into it it is set to 0 !! Your string is copied to the Label but otherwise lost.
So you need to come up with a suitable numeric value you use for the x-values..
And you also need to group the series you want to stack. For this there is a special property called StackedGroupName which serves to group those series that shall be stacked.
Here is how you can use it:
yourSeries1.SetCustomProperty("StackedGroupName", "Group1");
For a full example see this post !
It also shows one way of setting the Labels with string values of your choice..
This is the way to go for real StackedBar charts. Your workaround may or may not work. You could try to make the colors transparent or equal to the chart's backcolor; but it won't be more than a hack, imo.
Update
I guess I have misread the question. From what I see you do not really want to create a stacked chart.
Instead you struggle with these issues:
displaying bars at the same y-spot
making some bars invisible
displaying a vertical line as a marker
Let's tackle each:
Some column types including all Bars, Columns and then some have a little known special property called DrawSideBySide.
By default is is set to Auto, which will work like True. This is usually fine as we don't want bars to sit upon each other, effectively hiding all or part of the overlaid points.
But here we do want them to share the same y-position, so we need to set the property to false for at least one Series; the others (on Auto) will follow..:
You can do it either like this:
aSeries["DrawSideBySide"] = "false";
or like this:
aSeries.SetCustomProperty("DrawSideBySide", "false");
Next we hide the overlaid Series; this is simple:
aSeries.Color = Color.Transparent;
The last issue is displaying a line marker. There is no such MarkerStyle, so we need to use a custom style. For this we need to create a suitable bitmap and add it as a NamedImage to the chart's Images collection.
This sounds more complicated than it is; however the MarkerImage will not be scaled, so we need to created suitable sizes whenever we resize the Chart or add/remove points. I will ignore this complication for now..
int pointCount = 10;
Bitmap bmp = new Bitmap(2, chart.ClientSize.Height / pointCount - 5);
using (Graphics g = Graphics.FromImage(bmp)) g.Clear(Color.Black);
NamedImage marker = new NamedImage("marker", bmp);
chart.Images.Clear(); // quick & dirty
chart.Images.Add(marker);
Here is the result:
A few notes:
I would recommend to use variables for all chart elements you refer to repeatedly instead of using indexed references all the time. Less code, easier to read, a lot easier to maintain, and probably better performance.
Since your code called for the visible datapoints to be either red or green the Legend will not show a good representation. See here for an example of drawing a multi-colored legend item..
I used the chart height; this is not really recommended as there may be Titles or Legends or even more ChartAreas; instead you should use the height of the ChartArea, or even more precise, the height of the InnerPlotPosition. You would need to convert those from percentages to pixels. Not too hard, see below or see here
or here for more examples!
The markers should be adapted from the Resize and probably from the AxisViewChanged events. Putting it in a nice function to call (e.g. void setMarkerImage(Chart chart, Series s, string name, int width, Color c)) is always a good idea.
If you need to adapt the size of the marker image repeatedly, you may want to write better code for clearing the old one; this should include disposing of the Bitmap that was used before..
Here is an example:
var oldni = chart.Images.FindByName("marker");
if (oldni != null)
{
oldni.Image.Dispose();
chart.Images.Remove(oldni);
oldni.Dispose();
}
In some situations one needs to nudge the Chart to update some of its properties; RecalculateAxesScale is one such nudge.
Example for calculating a suitable marker height:
ChartArea ca = chart.ChartAreas[0];
ca.RecalculateAxesScale();
float cah = ca.Position.Height;
float iph = ca.InnerPlotPosition.Height;
float h = chart3.ClientSize.Height * cah / 100f * iph / 100f;
int mh = (int)(h / s.Points.Count);
Final note: The original answer stressed the importance of using meaningful x-values. Strings are useless! This was important for stacking bars; but it is equally important now, when we want bars to sit at the same vertical positions! Adding the x-values as strings is again resulting in nonsense..
(Since we have Bars the x-values go along the vertical axis and vice versa..)

c# .NET green screen background remove

I am working on a photo software for desktop PC that works on Windows 8. I would like to be able to remove the green background from the photo by means of chroma keying.
I'm a beginner in image manipulation, i found some cool links ( like http://www.quasimondo.com/archives/000615.php ), but I can't transale it in c# code.
I'm using a webcam (with aforge.net) to see a preview and take a picture.
I tried color filters but the green background isn't really uniform, so this doesn't work.
How to do that properly in C#?
It will work, even if the background isn't uniform, you'll just need the proper strategy that is generous enough to grab all of your greenscreen without replacing anything else.
Since at least some links on your linked page are dead, I tried my own approach:
The basics are simple: Compare the image pixel's color with some reference value or apply some other formula to determine whether it should be transparent/replaced.
The most basic formula would involve something as simple as "determine whether green is the biggest value". While this would work with very basic scenes, it can screw you up (e.g. white or gray will be filtered as well).
I've toyed around a bit using some simple sample code. While I used Windows Forms, it should be portable without problems and I'm pretty sure you'll be able to interpret the code. Just note that this isn't necessarily the most performant way to do this.
Bitmap input = new Bitmap(#"G:\Greenbox.jpg");
Bitmap output = new Bitmap(input.Width, input.Height);
// Iterate over all piels from top to bottom...
for (int y = 0; y < output.Height; y++)
{
// ...and from left to right
for (int x = 0; x < output.Width; x++)
{
// Determine the pixel color
Color camColor = input.GetPixel(x, y);
// Every component (red, green, and blue) can have a value from 0 to 255, so determine the extremes
byte max = Math.Max(Math.Max(camColor.R, camColor.G), camColor.B);
byte min = Math.Min(Math.Min(camColor.R, camColor.G), camColor.B);
// Should the pixel be masked/replaced?
bool replace =
camColor.G != min // green is not the smallest value
&& (camColor.G == max // green is the biggest value
|| max - camColor.G < 8) // or at least almost the biggest value
&& (max - min) > 96; // minimum difference between smallest/biggest value (avoid grays)
if (replace)
camColor = Color.Magenta;
// Set the output pixel
output.SetPixel(x, y, camColor);
}
}
I've used an example image from Wikipedia and got the following result:
Just note that you might need different thresholds (8 and 96 in my code above), you might even want to use a different term to determine whether some pixel should be replaced. You can also add smoothening between frames, blending (where there's less green difference), etc. to reduce the hard edges as well.
I've tried Mario solution and it worked perfectly but it's a bit slow for me.
I looked for a different solution and I found a project that uses a more efficient method here.
Github postworthy GreenScreen
That project takes a folder and process all files, I just need an image so I did this:
private Bitmap RemoveBackground(Bitmap input)
{
Bitmap clone = new Bitmap(input.Width, input.Height, PixelFormat.Format32bppArgb);
{
using (input)
using (Graphics gr = Graphics.FromImage(clone))
{
gr.DrawImage(input, new Rectangle(0, 0, clone.Width, clone.Height));
}
var data = clone.LockBits(new Rectangle(0, 0, clone.Width, clone.Height), ImageLockMode.ReadWrite, clone.PixelFormat);
var bytes = Math.Abs(data.Stride) * clone.Height;
byte[] rgba = new byte[bytes];
System.Runtime.InteropServices.Marshal.Copy(data.Scan0, rgba, 0, bytes);
var pixels = Enumerable.Range(0, rgba.Length / 4).Select(x => new {
B = rgba[x * 4],
G = rgba[(x * 4) + 1],
R = rgba[(x * 4) + 2],
A = rgba[(x * 4) + 3],
MakeTransparent = new Action(() => rgba[(x * 4) + 3] = 0)
});
pixels
.AsParallel()
.ForAll(p =>
{
byte max = Math.Max(Math.Max(p.R, p.G), p.B);
byte min = Math.Min(Math.Min(p.R, p.G), p.B);
if (p.G != min && (p.G == max || max - p.G < 7) && (max - min) > 20)
p.MakeTransparent();
});
System.Runtime.InteropServices.Marshal.Copy(rgba, 0, data.Scan0, bytes);
clone.UnlockBits(data);
return clone;
}
}
Do not forget to dispose of your Input Bitmap and the return of this method.
If you need to save the image just use the Save instruction of Bitmap.
clone.Save(#"C:\your\folder\path", ImageFormat.Png);
Here you can find methods to process an image even faster.Fast Image Processing in C#
Chromakey on a photo should assume an analog input. In the real world, exact values are very rare.
How do you compensate for this? Provide a threshold around the green of your choice in both hue and tone. Any colour within this threshold (inclusive) should be replaced by your chosen background; transparent may be best. In the first link, the Mask In and Mask Out parameters achieve this. The pre and post blur parameters attempt to make the background more uniform to reduce encoding noise side effects so that you can use a narrower (preferred) threshold.
For performance, you may want to write a pixel shader to zap the 'green' to transparent but that is a consideration for after you get it working.

Histogram Plot for RGB values in WinRT App

I'm having an issue with creating a histogram representation of an image in a WinRT app. What I'd like to make consists of four histogram plots for Red, Green, Blue, Luminosity for an image.
My main issue is how to actually draw a picture of that Histogram so I could show it on the screen. My code so far is pretty... messy, I've searched a lot for this topic, mostly my results consisted of code in Java, which I'm trying somehow to translate it in C#, but API is pretty different... Had an attempt from AForge as well but that's winforms...
Here's my messy code, I know it looks bad but I'm striving to make this work first :
public static WriteableBitmap CreateHistogramRepresentation(long[] histogramData, HistogramType type)
{
//I'm trying to determine a max height of a histogram bar, so
//I could determine a max height of the image that then I'll remake it
//at a lower resolution :
var max = histogramData[0];
//Determine the max value, the highest bar in the histogram, the initial height of the image.
for (int i = 0; i < histogramData.Length; i++)
{
if (histogramData[i] > max)
max = histogramData[i];
}
var bitmap = new WriteableBitmap(256, 500);
//Set a color to draw with according to the type of the histogram :
var color = Colors.White;
switch (type)
{
case HistogramType.Blue :
{
color = Colors.RoyalBlue;
break;
}
case HistogramType.Green:
{
color = Colors.OliveDrab;
break;
}
case HistogramType.Red:
{
color = Colors.Firebrick;
break;
}
case HistogramType.Luminosity:
{
color = Colors.DarkSlateGray;
break;
}
}
//Compute a scaler to scale the bars to the actual image dimensions :
var scaler = 1;
while (max/scaler > 500)
{
scaler++;
}
var stream = bitmap.PixelBuffer.AsStream();
var streamBuffer = new byte[stream.Length];
//Make a white image initially :
for (var i = 0; i < streamBuffer.Length; i++)
{
streamBuffer[i] = 255;
}
//Color the image :
for (var i = 0; i < 256; i++) // i = column
{
for (var j = 0; j < histogramData[i] / scaler; j++) // j = line
{
streamBuffer[j*256*4 + i] = color.B; //the image has a 256-pixel width
streamBuffer[j*256*4 + i + 1] = color.G;
streamBuffer[j*256*4 + i + 2] = color.R;
streamBuffer[j*256*4 + i + 2] = color.A;
}
}
//Write the Pixel Data into the Pixel Buffer of the future Histogram image :
stream.Seek(0, 0);
stream.Write(streamBuffer, 0, streamBuffer.Length);
return bitmap.Flip(WriteableBitmapExtensions.FlipMode.Horizontal);
}
This creates a pretty bad histogram representation, it doesn't even colour it with an corresponding colour... It's not working properly, I'm working on it to fix it...
If you can contribute with a link you might know any code for a histogram representation for WinRT apps or everything else is greatly appreciated.
While you could use a charting control as JP Alioto pointed out, histograms tend to represent a lot of data. In your sample alone you're rendering 256 bars * 4 axis (R,G,B,L). The problem with charting controls is that they usually like to be handed collections (or arrays) of hydrated data, which they draw, and which they tend to keep in memory. A histogram like yours would need to have 1024 objects in memory (256 * 4) and passed to the chart as a whole. It's just not a good use of memory management.
The alternative of course is to draw it yourself. But as you've found, pixel-by-pixel drawing can be a bit of a pain. The best answer - in my opinion - is to agree with Shahar and recommend you use WriteableBitmapEx on CodePlex.
http://writeablebitmapex.codeplex.com
WriteableBitmapEx includes methods for drawing shapes like lines and rectangles that are very very fast. You can draw the data as you enumerate it (instead of having to have it all in memory at one time) and the result is a nice compact image that is already "bitmap cached" (meaning it renders very fast since it doesn't have to redrawn on each frame).
Dev support, design support and more awesome goodness on the way: http://bit.ly/winappsupport

Implement faster graphics operation on WPF Canvas

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

Categories