Need something to draw many images quickly in WPF - c#

I'm working on Generator of cellular automata in .NET 3.5. I decided to use WPF because I wanted to learn something new in WPF.
I draw OneDimensional automata like Rule 30 and TwoDimensional like Life.
I need something to draw many images quick.
For example, dimensions of mesh is 64 x 64 and size of cell is 12px. So I draw 64*64 = 4096 images in one step. And interval between one step is about 100 ms.
I rewrite my application to the WinForms and there is everything ok. But in WPF is slow and I don't know why.
My example of drawing in WPF:
I have a class derived from Image. In this class I draw a bitmap and I use it in Source property.
public void DrawAll()
{
DrawingGroup dg = new DrawingGroup();
for (byte i = 0; i < this.DimensionX; ++i)
{
for (byte j = 0; j < this.DimensionY; ++j)
{
Piece p = this.Mesh[i][j];
Rect rect = new Rect(j * this.CellSize + (j + 1), i * this.CellSize + (i + 1),
this.CellSize, this.CellSize);
ImageDrawing id = new ImageDrawing();
id.Rect = rect;
if (p == null)
id.ImageSource = this._undefinedPiece.Image.Source;
else
id.ImageSource = p.Image.Source;
dg.Children.Add(id);
}
}
DrawingImage di = new DrawingImage(dg);
this.Source = di;
}
Second example of drawing in WPF:
I have derived class from Canvas and override OnRender function. Based on this article: http://sachabarber.net/?p=675
protected override void OnRender(DrawingContext dc)
{
base.OnRender(dc);
for (byte i = 0; i < this.DimensionX; ++i)
{
for (byte j = 0; j < this.DimensionY; ++j)
{
Rect rect = new Rect(j * this.CellSize + (j + 1), i * this.CellSize + (i + 1),
this.CellSize, this.CellSize);
BitmapImage bi;
int counter = i + j + DateTime.Now.Millisecond;
if (counter % 3 == 0)
bi = this.bundefined;
else if (counter % 3 == 1)
bi = this.bwhite;
else
bi = this.bred;
dc.DrawImage(bi, rect);
++counter;
}
}
}
Thanks for all replies

As written in this article, the fastest way to draw 2D in wpf is the StreamGeomerty.
I'm sure you're able to accomodate your pattern generation logic to the needs of this class.

WPF real power is in rendering vector. Is there a way that instead of using Image, you can create rectangle objects of a predetermined size assign color and then assign location? Bitmap is pretty resource intensive to work with vs vector. Even though WPF pushes the rendering off to the video card, it is still a lot of processing.

You can create an 'Icon Font' which would contain your images instead of typical characters. Displaying images through the font has no difference from using it for regular text.
<TextBlock Text="&#xE726" FontFamily="Segoe MDL2 Assets" Foreground="Red"/>
Advantages:
Glyphs scale losslessly
Different colors can be set
Better performance then Shapes / Paths

Related

Plotting 2D heat map

I have a chart on which I want to plot a heat map; the only data I have is humidity and temperature, which represent a point in the chart.
How do I get the rectangular type of heat map on the chart in c#?
What I want is similar to picture below :
What I really want is a rectangular region in the chart which is plotted in different color based on the point that i get from the list of points and form the colorful section in the chart.
You have a choice of at least three ways to create a chart with colored rectangles that make up a heat map.
Here is one example
that uses/abuses a DataGridView. While I would not suggest this, the post contains a useful function that creates nice color lists to use in your task.
Then there is the option to draw the chart using GDI+ methods, namely Graphics.FillRectangle. This not hard at all but once you want to get those nice extras a Chart control offers, like scaling, axes, tooltips etc the work adds up.. See below!
So let's have a look at option three: Using the Chart control from the DataVisualization namespace.
Let's first assume that you have created a list of colors:
List<Color> colorList = new List<Color>();
And that you have managed to project your data onto a 2D array of int indices that point into the color list:
int[,] coloredData = null;
Next you have to pick a ChartType for your Series S1 There really is only one I can think of that will help:
S1.ChartType = SeriesChartType.Point;
Points are displayed by Markers. We want the DataPoints not really displayed as one of the standard MarkerTypes.
Square would be ok, if we wanted to display squares; but for rectangles it will not work well: Even if we let them overlap there will still be points at the borders that have a different size because they don't fully overlap..
So we use a custom marker by setting the MarkerImage of each point to a bitmap of a suitable size and color.
Here is a loop that adds the DataPoints to our Series and sets each to have a MarkerImage:
for (int x = 1; x < coloredData.GetLength(0); x++)
for (int y = 1; y < coloredData.GetLength(1); y++)
{
int pt = S1.Points.AddXY(x, y);
S1.Points[pt].MarkerImage = "NI" + coloredData[x,y];
}
This takes some explaining: To set a MarkerImage that is not at a path on the disk, it has to reside in the Chart's Images collection. This means is needs to be of type NamedImage. Any image will do, but it has to have a unique name string added to identify it in the NamedImagesCollection . I chose the names to be 'NI1', 'NI2'..
Obviously we need to create all those images; here is a function to do that:
void createMarkers(Chart chart, int count)
{
// rough calculation:
int sw = chart.ClientSize.Width / coloredData.GetLength(0);
int sh = chart.ClientSize.Height / coloredData.GetLength(1);
// clean up previous images:
foreach(NamedImage ni in chart1.Images) ni.Dispose();
chart.Images.Clear();
// now create count images:
for (int i = 0; i < count; i++)
{
Bitmap bmp = new Bitmap(sw, sh);
using (Graphics G = Graphics.FromImage(bmp))
G.Clear(colorList[i]);
chart.Images.Add(new NamedImage("NI" + i, bmp));
}
}
We want all markers to have at least roughly the right size; so whenever that size changes we set it again:
void setMarkerSize(Chart chart)
{
int sx = chart1.ClientSize.Width / coloredData.GetLength(0);
int sy = chart1.ClientSize.Height / coloredData.GetLength(1);
chart1.Series["S1"].MarkerSize = (int)Math.Max(sx, sy);
}
This doesn't care much about details like the InnerPlotPosition, i.e. the actual area to draw to; so here is some room for refinement..!
We call this when we set up the chart but also upon resizing:
private void chart1_Resize(object sender, EventArgs e)
{
setMarkerSize(chart1);
createMarkers(chart1, 100);
}
Let's have a look at the result using some cheap testdata:
As you can see resizing works ok..
Here is the full code that set up my example:
private void button6_Click(object sender, EventArgs e)
{
List<Color> stopColors = new List<Color>()
{ Color.Blue, Color.Cyan, Color.YellowGreen, Color.Orange, Color.Red };
colorList = interpolateColors(stopColors, 100);
coloredData = getCData(32, 24);
// basic setup..
chart1.ChartAreas.Clear();
ChartArea CA = chart1.ChartAreas.Add("CA");
chart1.Series.Clear();
Series S1 = chart1.Series.Add("S1");
chart1.Legends.Clear();
// we choose a charttype that lets us add points freely:
S1.ChartType = SeriesChartType.Point;
Size sz = chart1.ClientSize;
// we need to make the markers large enough to fill the area completely:
setMarkerSize(chart1);
createMarkers(chart1, 100);
// now we fill in the datapoints
for (int x = 1; x < coloredData.GetLength(0); x++)
for (int y = 1; y < coloredData.GetLength(1); y++)
{
int pt = S1.Points.AddXY(x, y);
// S1.Points[pt].Color = coloredData[x, y];
S1.Points[pt].MarkerImage = "NI" + coloredData[x,y];
}
}
A few notes on limitations:
The point will always sit on top of any gridlines. If you really needs those you will have to draw them on top in one of the the Paint events.
The labels as shown are referring to the integers indices of the data array. If you want to show the original data, one way would be to add CustomLabels to the axes.. See here for an example!
This should give you an idea of what you can do with a Chart control; to complete your confusion here is how to draw those rectangles in GDI+ using the same colors and data:
Bitmap getChartImg(float[,] data, Size sz, Padding pad)
{
Bitmap bmp = new Bitmap(sz.Width , sz.Height);
using (Graphics G = Graphics.FromImage(bmp))
{
float w = 1f * (sz.Width - pad.Left - pad.Right) / coloredData.GetLength(0);
float h = 1f * (sz.Height - pad.Top - pad.Bottom) / coloredData.GetLength(1);
for (int x = 0; x < coloredData.GetLength(0); x++)
for (int y = 0; y < coloredData.GetLength(1); y++)
{
using (SolidBrush brush = new SolidBrush(colorList[coloredData[x,y]]))
G.FillRectangle(brush, pad.Left + x * w, y * h - pad.Bottom, w, h);
}
}
return bmp;
}
The resulting Bitmap looks familiar:
That was simple; but to add all the extras into the space reserved by the padding will not be so easy..

"Parameter must be positive and < Height. Parameter name: y" error

I am developing a component for Grasshopper 3D, which is a Rhino (architecture) plugin.
Using the Render() method, this draws a heatmap image onto the canvas. Isolating my other methods and constructors, I highly believe this method is causing my issue.
protected override void Render(Grasshopper.GUI.Canvas.GH_Canvas canvas, Graphics graphics, Grasshopper.GUI.Canvas.GH_CanvasChannel channel) {
// Render the default component.
base.Render(canvas, graphics, channel);
// Now render our bitmap if it exists.
if (channel == Grasshopper.GUI.Canvas.GH_CanvasChannel.Wires) {
KT_HeatmapComponent comp = Owner as KT_HeatmapComponent;
if (comp == null)
return;
List<HeatMap> maps = comp.CachedHeatmaps;
if (maps == null)
return;
if (maps.Count == 0)
return;
int x = Convert.ToInt32(Bounds.X + Bounds.Width / 2);
int y = Convert.ToInt32(Bounds.Bottom + 10);
for (int i = 0; i < maps.Count; i++) {
Bitmap image = maps[i].Image;
if (image == null)
continue;
Rectangle mapBounds = new Rectangle(x, y, maps[i].Width * 10, maps[i].Height * 10);
mapBounds.X -= mapBounds.Width / 2;
Rectangle edgeBounds = mapBounds;
edgeBounds.Inflate(4, 4);
GH_Capsule capsule = GH_Capsule.CreateCapsule(edgeBounds, GH_Palette.Normal);
capsule.Render(graphics, Selected, false, false);
capsule.Dispose();
// Unnecessary graphics.xxxxxx methods and parameters
y = edgeBounds.Bottom + 10;
}
}
}
The error I receive when I attempt to render things onto my canvas is:
1. Solution exception:Parameter must be positive and < Height.
Parameter name: y
From my research, it appears to happen the most when you encounter array overflows.
My research links:
http://www.codeproject.com/Questions/158055/Help-in-subtraction-of-two-images
Exception on traveling through pixels BMP C#
http://www.c-sharpcorner.com/Forums/Thread/64792/
However, the above examples apply mostly to multi-dimensional arrays, while I have a one-dimensional.
I was wondering if anyone else has had this issue before, and could give me some pointers and guidance?
Thanks.
int x = Convert.ToInt32(Bounds.X + Bounds.Width / 2);
int y = Convert.ToInt32(Bounds.Bottom + 10);
Your error is telling you that y must be less than the height, but you are setting it to 10 more than your height because you are adding to the Bounds.Bottom.
maps[i].Height * 10
You also need to make sure that your calculated Height is what you think it is and compare that y is valid against it.

indirectly draw to panel from another function

I am creating Conway's Game of life in C# and everything works fine, except the refreshing of the panel to display the next generation. I can draw to the panel to establish grid lines for the cells, place and destroy "life" using simWindow_Paint and simWindow_MouseClick but at present i cannot update it.
private void updateGame(){
int living = 0;
int dead = 0;
for(int i = 1; i < gameHeight; i++)
for (int j = 1; j < gameWidth; j++){
//set cell location and size
Rectangle cell = new Rectangle();
cell.Height = cell.Width = 9;
int X = j - 1;
int Y = i - 1;
cell.X = Convert.ToInt32(X * 10 + 1);
cell.Y = Convert.ToInt32(Y * 10 + 1);
using (Graphics g = this.simWindow.CreateGraphics()){
if (gameArray[i, j] == true){
Brush brush = new SolidBrush(Color.Red);
g.FillRectangle(brush, cell);
++living;
} else {
Brush brush = new SolidBrush(Color.White);
g.FillRectangle(brush, cell);
++dead;
}
}
}
}
I am not sure if what i am attempting to do is possible in C#, I have already done this in Pascal, but i am not sure if C# works the same way...
Difficult to say with what you've shown us, but I suspect the problem might be in your simWindow_Paint method. This is (presumably) responding to the paint event for your panel and I suspect what you are doing is overwriting (or over painting) all the drawing you are doing in your updateGame() method.
All the drawing should be done in your paint handler. So you paint handler should know the state of the game and draw it accordingly. Your updateGame() method should just update the state of the game and then invalidate the panel (forcing a repaint).
I recommend you review the following documentation on custom painting in WinForms: http://msdn.microsoft.com/en-us/library/kxys6ytf.aspx

Cannot draw an image using this program?

I am trying to process an image for finding out red color regions in it. I scan pixels and if they are found to be ENOUGH red, they are converted to black, otherwise white.
To speed up the process, I skip certain pixels, and need to draw blocks of black or white at their place. I am using this function but it seems to be wrong somewhere. The image I get in the end is completely blank.
public void ProcessFrame( ref Bitmap image )
{
int skip = 10;
Graphics g = Graphics.FromImage(System.Drawing.Image.FromHbitmap(image.GetHbitmap()));
SolidBrush black = new SolidBrush(Color.Black);
SolidBrush white = new SolidBrush(Color.White);
for (int i = 0; i < image.Width; i=i+skip)
{
for (int j = 0; j < image.Height; j = j + skip)
{
Color cl = image.GetPixel(i, j);
if (cl.R > (cl.G * 2) && cl.R > (cl.B * 2))
{
g.FillRectangle(black, new Rectangle(i, j, skip, skip));
}
else
{
g.FillRectangle(white, new Rectangle(i, j, skip, skip));
}
}
}
}
Can you point out the mistake, OR any other better method to achieve what I aim for?
By blank, do you mean white?
I don't know your image, but if (cl.R > (cl.G * 2) && cl.R > (cl.B * 2)) is not a good test for redness. #010000 passes that test, but is basically black. And, #ffaaaa looks red, but won't pass.
If you had a very dark image, lots of pixels might pass that test that aren't very red. With a light image, lots of red pixels won't pass.
Probably the best way is to convert the color the HSL, and then use values of H(ue) that are in the red zone, but only if S(aturation) and L(uminance) are sufficiently bright and non-gray (over a threshhold to not just be black or gray).
Try to use:
Graphics g = Graphics.FromImage(image);
Then consider other replies to improve your color testing.
You didn't show where image is created, but this code will draw into a bitmap:
var bmp = new Bitmap(200,200);
using (g = Graphics.FromImage(bmp))
{
g.FillEllipse(Brushes.Red, 10, 10, 50, 50);
}
Note that you need to call Graphics.Dispose (or use using) before using the image.
Also note that using GetPixel for image processing will be very slow. Use low-level memory access (Bitmap.LockBits) or try some image processing library for .NET (like AForge).

Algorithm for finding a painted region on a canvas

Update: I am attempting to pull a little clutter out of this post and sum it up more concisely. Please see the original edit if needed.
I am currently attempting to trace a series of single colored blobs on a Bitmap canvas.
e.g. An example of the bitmap I am attempting to trace would look like the following:
alt text http://www.refuctored.com/polygons.bmp
After successfully tracing the outlines of the 3 blobs on the image, I would have a class that held the color of a blob tied to a point list representing the outline of the blob (not all the pixels inside of the blobs).
The problem I am running into is logic in instances where a neighboring pixel has no surrounding pixels other than the previous pixel.
e.g The top example would trace fine, but the second would fail because the pixel has no where to go since the previous pixels have already been used.
alt text http://www.refuctored.com/error.jpg
I am tracing left-to-right, top-to-bottom, favoring diagonal angles over right angles. I must be able to redraw an exact copy of the region based off the data I extract, so the pixels in the list must be in the right order for the copy to work.
Thus far, my attempt has been riddled with failure, and a couple days of pulling my hair out trying to rewrite the algorithms a little different each time to solve the issue. Thus far I have been unsuccessful. Has anyone else had a similar issue like mine who has a good algorithm to find the edges?
One simple trick to avoiding these cul-de-sacs is to double the size of the image you want to trace using a nearest neighbor scaling algorithm before tracing it. Like that you will never get single strips.
The alternative is to use a marching squares algorithm - but it seems to still have one or two cases where it fails: http://www.sakri.net/blog/2009/05/28/detecting-edge-pixels-with-marching-squares-algorithm/
Have you looked at blob detection algorithms? For example, http://opencv.willowgarage.com/wiki/cvBlobsLib if you can integrate OpenCV into your application. Coupled with thresholding to create binary images for each color (or color range) in your image, you could easily find the blobs that are the same color. Repeat for each color in the image, and you have a list of blobs sorted by color.
If you cannot use OpenCV directly, perhaps the paper referenced by that library ("A linear-time component labeling algorithm using contour tracing technique", F.Chang et al.) would provide a good method for finding blobs.
Rather than using recursion, use a stack.
Pseudo-code:
Add initial pixel to polygon
Add initial pixel to stack
while(stack is not empty) {
pop pixel off the stack
foreach (neighbor n of popped pixel) {
if (n is close enough in color to initial pixel) {
Add n to polygon
Add n to stack
}
}
}
This will use a lot less memory than the same solution using recursion.
Just send your 'Image' to BuildPixelArray function and then call the FindRegions.
After that the 'colors' variable will be holding your colors list and pixel coordinates in every list member.
I've copied the source from one of my projects, there may be some undefined variables or syntax erors.
public class ImageProcessing{
private int[,] pixelArray;
private int imageWidth;
private int imageHeight;
List<MyColor> colors;
public void BuildPixelArray(ref Image myImage)
{
imageHeight = myImage.Height;
imageWidth = myImage.Width;
pixelArray = new int[imageWidth, imageHeight];
Rectangle rect = new Rectangle(0, 0, myImage.Width, myImage.Height);
Bitmap temp = new Bitmap(myImage);
BitmapData bmpData = temp.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
int remain = bmpData.Stride - bmpData.Width * 3;
unsafe
{
byte* ptr = (byte*)bmpData.Scan0;
for (int j = 15; j < bmpData.Height; j++)
{
for (int i = 0; i < bmpData.Width; i++)
{
pixelArray[i, j] = ptr[0] + ptr[1] * 256 + ptr[2] * 256 * 256;
ptr += 3;
}
ptr += remain;
}
}
temp.UnlockBits(bmpData);
}
public void FindRegions()
{
colors = new List<MyColor>();
for (int i = 0; i < imageWidth; i++)
{
for (int j = 0; j < imageHeight; j++)
{
int tmpColorValue = pixelArray[i, j];
MyColor tmp = new MyColor(tmpColorValue);
if (colors.Contains(tmp))
{
MyColor tmpColor = (from p in colors
where p.colorValue == tmpColorValue
select p).First();
tmpColor.pointList.Add(new MyPoint(i, j));
}
else
{
tmp.pointList.Add(new MyPoint(i, j));
colors.Add(tmp);
}
}
}
}
}
public class MyColor : IEquatable<MyColor>
{
public int colorValue { get; set; }
public List<MyPoint> pointList = new List<MyPoint>();
public MyColor(int _colorValue)
{
colorValue = _colorValue;
}
public bool Equals(MyColor other)
{
if (this.colorValue == other.colorValue)
{
return true;
}
return false;
}
}
public class MyPoint
{
public int xCoord { get; set; }
public int yCoord { get; set; }
public MyPoint(int _xCoord, int _yCoord)
{
xCoord = _xCoord;
yCoord = _yCoord;
}
}
If you're getting a stack overflow I would guess that you're not excluding already-checked pixels. The first check on visiting a square should be whether you've been here before.
Also, I was working on a related problem not too long ago and I came up with a different approach that uses a lot less memory:
A queue:
AddPointToQueue(x, y);
repeat
x, y = HeadItem;
AddMaybe(x - 1, y); x + 1, y; x, y - 1; x, y + 1;
until QueueIsEmpty;
AddMaybe(x, y):
if Visited[x, y] return;
Visited[x, y] = true;
AddPointToQueue(x, y);
The point of this approach is that you end up with your queue basically holding a line wrapped around the mapped area. This limits memory usage better than a stack can.
If relevant it also can be trivially modified to yield the travel distance to any square.
Try using AForge.net. I would go for Filter by colors, Threshold and then you could do some Morphology to decrement the black/White zones to lose contact between the objects. Then you could go for the Blobs.

Categories

Resources