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);
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!
In c# I can fill a polygon in a bitmap image as following.
using (Graphics g = Graphics.FromImage(bitmap))
{
g.FillPolygon(colorBrush, points.ToArray());
}
FillPolygon method fills the pixels inside the polygon, in this case, the white pixels and the black pixels remains the same.
Now, I want just the opposite of this operation. That means, exterior pixels will be filled and interior pixels will remain the same. I this case, black pixels are exterior pixels.
Edit
I need this because let's say, I have a binary image of an object. I need to clip the pixels with background color(black) and the pixels inside the white polygon will remain unchanged.
You can do this by using a GraphicsPath as follows:
Add the polygon to the path.
Add a rectangle to the path which encompasses the area you want to "invert".
Use Graphics.FillPath() to fill the path.
For an example program, create a default Windows Forms app and override OnPaint() as follows:
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
var points = new []
{
new PointF(150, 250),
new PointF( 50, 500),
new PointF(250, 400),
new PointF(300, 100),
new PointF(500, 500),
new PointF(500, 50),
};
using (var path = new GraphicsPath())
{
path.AddPolygon(points);
// Uncomment this to invert:
// p.AddRectangle(this.ClientRectangle);
using (var brush = new SolidBrush(Color.Black))
{
e.Graphics.FillPath(brush, path);
}
}
}
If you run that (and resize the window) you'll see a black shape inside a white window.
Uncomment the indicated line and run the program and you'll see a white shape inside a black window (i.e. adding the ClientRectangle inverted it).
if i draw some rectangles in panel , how can i select one of them and delete it. My code here i have write it inside panel_OnClick event :
g = panel1.CreateGraphics();
Pen p = new Pen(Color.Black);
p.Width = 2;
g.DrawRectangle(p, e.X, e.Y, 100, 60);
p.Dispose();
g.Dispose();
Drawing on the panel is like drawing on a piece of paper - they are etched in and are no longer a rectangle, but a collection of pixels. Even though you could draw a rectangle over the one you want to clear using the background color, you won't be "removing the rectangle", you'll just draw a rectangle over the existing one.
Rectangle will have a Region.
You will need to subscribe to one of the following: MouseClick, MouseDown, MouseUp.
// assuming you keep a reference of the rectangle
void OnMouseClick(object sender, MouseEventArgs e) {
if(myRect.Region.IsVisible(e.Location) {
// perform action on myRect ...
// have window Invalidate(myRect)
// Refresh() the invalidated area.
}
}
This snippet assumes that no Rectangles overlap. You can also create a GraphicsPath from the points of the Rectangle and then from that path, I believe you can create a Region that enables the actual lines of the rectangle to be selected.
Update per comment
Region
GraphicsPath
I checked and I didn't see the Region property for Rectangle. So, to create the Region do the following:
var gPath = new GraphicsPath();
gPath.AddRectangle(rectangle);
var region = new Region(gPath);
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'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);
}