I'm using .NET to draw a diagram, and I want to highlight objects when the user performs a click on them. It is easy when a figure is fully contained in a rectangle:
if (figure.Bounds.Contains(p)) // bounds is a rectangle
But I don't know how to manage it if the figure is a complex GraphicsPath.
I have defined the following GraphicsPath for the figure (the green circle).
I want to highlight the figure when the user click on it. I would like to know if a Point is contained in that GraphicsPath.
Any ideas? Thanks in advance.
I don't know a DrawingPath (you mean probably; graphics.DrawPath) but a GraphicsPath has the IsVisible method to check if a point is in the path.
bool isInPath = graphicsObj.IsVisible(point)
Using both .IsOutlineVisible and .IsVisible together cover the whole thing, border and within border, for this rectangle example, but as you know GraphicsPath can works for different shapes.
bool b = gp.IsVisible(point) || gp.IsOutlineVisible(point, pen);
For it in code
Rectangle r = new Rectangle(new Point(50, 100), new Size(500, 100));
bool b;
// say Point p is set.
// say Pen pen is set.
using (var gp = new GraphicsPath())
using (var pen = new Pen(Color.Black, 44)) {
gp.AddRectangle(r);
bool b = gp.IsVisible(point) || gp.IsOutlineVisible(point, pen);
}
Related
I am new to drawing and paints in c# & I am trying to make a simple program it has 3 intersecting circles (A,B,C). What i want to do is paint a certain (according to result I get).
For example: If I get 1 as a result I want to fill the yellow bordered region, if I get 4 I want to fill green bordered region and so on.
My Code to draw these circles:
private void button1_Click(object sender, EventArgs e)
{
Graphics A = this.CreateGraphics();
Graphics B = this.CreateGraphics();
Graphics C = this.CreateGraphics();
Pen Bluepen = new Pen(Color.Blue, 2);
Pen RedPen = new Pen(Color.Red, 2);
Pen BlackPen = new Pen(Color.Black, 2);
A.DrawEllipse(Bluepen,100, 100, 150, 150);
B.DrawEllipse(RedPen, 195, 100, 150, 150);
C.DrawEllipse(BlackPen, 145, 190, 150, 150);
}
Since you are new to this topic I have to tell you: This is a lot harder that one would hope for.
Three solutions come to mind:
Construct a GraphicsPath you could fill from three Arcs. To calculate the arcs you need the rectangles you have but also the sweeping angle and also the starting angle. This will take quite some math..
After having drawn into a Bitmap you could floodfill the area you want to color. This will only work for bitamps from which you can extract the current color of each pixel, not for drawing onto controls..
The simplest way it still a bit involved, but only mildly so
Solution 3 (Create a Region and fill it)
You can use all sorts of set operations to combine areas called Regions. And you can construct a Region from a GraphicsPath. And you can construct a GraphicsPath by adding an ellipse. And you can clip the drawing area of a Graphics object to a Region.
Let's try:
private void panel1_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
Rectangle r1 = new Rectangle(100, 100, 150, 150);
Rectangle r2 = new Rectangle(195, 100, 150, 150);
Rectangle r3 = new Rectangle(145, 190, 150, 150);
GraphicsPath gp1 = new GraphicsPath();
GraphicsPath gp2 = new GraphicsPath();
GraphicsPath gp3 = new GraphicsPath();
gp1.AddEllipse(r1);
gp2.AddEllipse(r2);
gp3.AddEllipse(r3);
Region r_1 = new Region(gp1);
Region r_2 = new Region(gp2);
Region r_3 = new Region(gp3);
r_1.Intersect(r_2); // just two of five..
r_1.Exclude(r_3); // set operations supported!
g.SetClip(r_1, CombineMode.Replace);
g.Clear(Color.Magenta); // fill the remaining region
g.ResetClip();
g.DrawEllipse(Pens.Red, r1);
g.DrawEllipse(Pens.Blue, r2);
g.DrawEllipse(Pens.Green, r3);
// finally dispose of all Regions and GraphicsPaths!!
r_1.Dispose();
gp1.Dispose();
.....
}
Do note that the region operations change the current region; if you want to fill more areas you need to restore the changed region!
Also note that I draw where any persistent drawing belongs: In the Paint event and that I use its e.Graphics object..
GraphicsPaths as Regions are GDI objects and should be disposed off!
Notes on solution 1 (Create a GraphicsPath by Math)
The full math is rather involved. By making a few assumptions the task can be greatly simplified: Let's assume the circles have the same size. Also that we first look at two circles only, with the same y-position. Finally that the circles form a symmetrical figure. (Which btw they don't: the red circle should have x=190 and the green one y=186,45..)
Getting the two intersection points as well as the sweeping angle is not so hard.
Next one can rotate the two points twice around the center of the whole figure by 120° using a Matrix; see here for an example. Now we have six points; we still need the smaller sweeping angle, which is also found with simple math.
Finally we can construct all 12 (!) GraphicsPaths from the 12 arcs and combine them at will.
The good part is that we can both fill and draw those paths. But, the code is rather extensive..
Notes on solution 2 (floodfill)
While you can't floodfill directly on a control you can prepare the result in a bitmap and then display that image on the control with Graphics.DrawImage.
For an example of coding a floodfill see this post!
I am learning about GraphicsPath and Region. And using it with Invalidate.
So, I have a Rectangle object and I want to erase this rectangle. But, I only want to erase the edge of the rectangle (that is, the lines).
At the moment I have this:
if(bErase)
{
Rectangle rcRubberBand = GetSelectionRectangle();
GraphicsPath path = new GraphicsPath();
path.AddRectangle(rcLastRubberBand);
Region reg = new Region(path);
myControl3.Invalidate(reg);
myControl3.Update();
}
It works, but it is invalidating the complete rectangle shape. I only need to invalidate the rectangle lines that I had drawn. Can I make such a path with GraphicsPath?
You can't get the system to invalidate anything but a full rectangle.
So you can't use an outline path to save time.
However it can be useful for other things. Let's look at two options :
You can create an outline path
You can exclude parts of a region
The simplest way to create an outline GraphicsPath is to widen a given path with a Pen:
GraphicsPath gp = new GraphicsPath();
gp.AddRectangle(r0);
using (Pen pen = new Pen(Color.Green, 3f)) gp.Widen(pen);
This let's you make use of all the many options of a Pen, including DashStyles, Alignment, LineJoins etc..
An alternative way is to create it with the default FillMode.Alternate and simply add a smaller figure:
Rectangle r0 = new Rectangle(11, 11, 333, 333);
Rectangle r1 = r0;
r1.Inflate(-6, -6);
GraphicsPath gp = new GraphicsPath();
gp.AddRectangle(r0);
gp.AddRectangle(r1);
Now you can fill the path
g.FillPath(Brushes.Red, gp);
or use it to clip the ClipBounds of a Graphics object g :
g.SetClip(gp);
After this anything you draw including a Clear will only affect the pixels inside the outline.
When you are done you can write:
g.ResetClip();
and continue drawing on the full size of your graphics target.
Or you can use the path as the basis for a Region:
Region r = new Region(gp);
and restrict a Control to it..:
somecontrol.Region = r;
Regions support several set operations so instead of using the above outline path you could also write this with the same result:
Region r = new Region(r0);
r.Exclude(r1);
I'm using the GDI+ function FillClosedCurve (in C# if that matters), to draw a series of points as a nice "rounded" curve area. The problem is that it appears to be adding a strange "loop" shape to one corner of the resulting shape. The screenshot shows this little extra loop at the top right corner of my red coloured area -
the code is
g.FillClosedCurve(shapeBrush, shapePoints.ToArray(), FillMode.Winding, 0.4f);
g.DrawPolygon(blackPen, shapePoints.ToArray());
I added a black border with the DrawPolygon function so you can see where my coordinates are.
Can anyone tell me why I get that weird loop shape at the top right corner ??
Thank you.
It's caused by you specifying the same point more than once in the array i.e. as the first and last point.
FillClosedCurve "closes" the path for you....so there is no need...in fact it's incorrect to specify the point twice....as it will then try and close the path from a point back to the point at the same position....which causes the artifact.
Here's a little example to demonstrate the difference:
private void Form1_Paint(object sender, PaintEventArgs e)
{
PointF[] arrayDuplicatedPointAtStartAndEnd =
{
new PointF(20.0F, 20.0F),
new PointF(150.0F, 50.0F),
new PointF(150.0F, 150.0F),
new PointF(20.0F, 20.0F),
};
PointF[] arrayWithoutPointOverlap =
{
new PointF(20.0F, 20.0F),
new PointF(150.0F, 50.0F),
new PointF(150.0F, 150.0F)
};
float tension = 0.4F;
using (SolidBrush redBrush = new SolidBrush(Color.Red))
{
e.Graphics.FillClosedCurve(redBrush, arrayDuplicatedPointAtStartAndEnd, FillMode.Winding, tension);
}
e.Graphics.TranslateTransform(110.0f, 0.0f, MatrixOrder.Prepend);
using (SolidBrush blueBrush = new SolidBrush(Color.Blue))
{
e.Graphics.FillClosedCurve(blueBrush, arrayWithoutPointOverlap, FillMode.Winding, tension);
}
}
I have a paint program made in C#/GDI+ in which I draw different shapes with interchangeable colors and pen sizes on a panel. I have got the shape-drawing methods working OK, but when it comes to using a free pen (as you would in MS Paint) I have made a method that does the job, just quite ugly (see pic in link).
if (crtanje)
{
debljina = float.Parse(debljina_box.Text);
Graphics gr = Graphics.FromImage(bit);
gr.SmoothingMode = SmoothingMode.HighQuality;
olovka = new Pen(boja, debljina);
gr.DrawLine(olovka, new Point(prethodnoX ?? e.X, prethodnoY ?? e.Y), new Point(e.X, e.Y));
panel1.CreateGraphics().DrawImageUnscaled(bit, new Point(0, 0));
prethodnoX = e.X;
prethodnoY = e.Y;
}
Can this code be fixed to make drawing smoother or should I take some other approach?
the pic
I suppose you could iterate through a for loop and increase it by a very small amount so that it draws points more frequently and makes the line smoother. You could save the current point and calculate the next one, then draw a line between them. That's how you could make it smoother!
I am working on a program which I want to draw diode curves in a WinForms application. I have a list of diode names and I have theire points as you can see at the right side of the picture. That is Voltage as X and Current as Y ( A curve contains like 50 points).
What I want to do is by selecting one or more diodes from the list theire curve show up on my plot. What you see is just a picture box at the moment filled with a bmp. I know that this is not a reliable solution, so I am asking you what can be the best approach to do such thing? I dont know any good component which can make me do this. So I just need to know what can be the best approuch for this task?
A diode curve is something like:
I might have up to 100 of diode curves in my program which all of them (single or multiple) should be drawn by clicking on them in the list.
So what you think?
UPDATE
ALSO important thing is by deselecting a pin in the curve, its curve should be removed from the plot!
I am drawing that axis you see using the code below:
Bitmap xyCords = new Bitmap(500, 500);
Graphics g = Graphics.FromImage(xyCords);
g.DrawLine(penAxis, 250, 0, 250, 500);
g.DrawLine(penAxis, 0, 250, 500, 250);
curveBox.Image = xyCords;
how is it possible later if I made a new Graphics I append it like:
curveBox.Image += newGraphic;
** Please let me know if there is any component or something which already can do what I want. or else show me a good approach! Thanks!
Try using Graphics.DrawCurve. You just put all of the points you want in an array, and pass that and a pen to the method.
Edit:
Add this after your code to prove to yourself that both graphs coexist. To erase one or the other, just plot the same points, but in the background color of the bitmap (test for it, I don't remember what it is).
Point[] ptarray = new Point[3];
ptarray[0] = new Point(250, 250);
ptarray[1] = new Point(300, 300);
ptarray[2] = new Point(350, 400);
Pen pengraph = new Pen(Color.Green, 0.75F);
g.DrawCurve(pengraph, ptarray);
Point[] ptarray2 = new Point[3];
ptarray2[0] = new Point(100, 100);
ptarray2[1] = new Point(200, 150);
ptarray2[2] = new Point(250, 250);
Pen pengraph2 = new Pen(Color.Yellow, 1.25F);
g.DrawCurve(pengraph2, ptarray2);