GDI+ FillClosedCurve giving strange loop effect? - c#

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);
}
}

Related

How to paint a certain area

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!

C# fill out side of a polygon

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).

Transforming mouse coordinates

Im making a graph program and im stuck at where I need to get mouse coordinates to equal graphic scale. With picturebox I use transform to scale my graphic:
RectangleF world = new RectangleF(wxmin, wymin, wwid, whgt);
PointF[] device_points =
{
new PointF(0, PictureBox1.ClientSize.Height),
new PointF(PictureBox1.ClientSize.Width, PictureBox1.ClientSize.Height),
new PointF(0, 0),
};
Matrix transform = new Matrix(world, device_points);
gr.Transform = transform;
Im using MouseMove function. Is there a way to transform mouse coordinates? When I put my mouse on x=9 I need my mouse coordinate to be 9.
private void PictureBox1_MouseMove(object sender, MouseEventArgs e)
{
Console.WriteLine(e.X);
}
As Hans' comment implies, you can use a second Matrix to accomplish this. You can obtain it either by copying the original Matrix and calling the copy's Invert() method, or you can create the new Matrix from scratch by reversing your input rectangles from the original.
IMHO inverting is easier, but it does mean you'll need to create the inverse matrix and store it somewhere. E.g.:
Matrix transform = new Matrix(world, device_points);
gr.Transform = transform;
inverseTransform = transform.Clone();
inverseTransform.Invert();
where inverseTransform is a field in your class rather than a local variable, so that your mouse-handling code can use it later.
If you must construct the Matrix later, you could do it like this:
RectangleF device = new RectangleF(new Point(), PictureBox1.ClientSize);
PointF[] world_points =
{
new PointF(wxmin, wymin + whgt),
new PointF(wxmin + wwid, wymin + whgt),
new PointF(wxmin, wymin),
};
Matrix inverseTransform = new Matrix(device, world_points);
In either case, you'd simply use the Matrix.TransformPoints() method in your mouse-handling code to apply the inverse transform to the mouse coordinates to get back to your world coordinates.

c# - Smoother drawing pen

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!

How do I display an image within a region defined in a Picturebox?

As a follow on to my previous question I have gotten my region but spent the last two hours trying to display tiny pictures wihing that region alone; with the end goal being to be able to arbitarily display any number of images I choose.
so far this is my code:
void OnPaintRadar(Object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
Bitmap blip = new Bitmap(tst_Graphics.Properties.Resources.dogtag);
Rectangle radar_rect = new Rectangle(myRadarBox.Left + 80, myRadarBox.Left + 7, myRadarBox.Width - 200, myRadarBox.Height + 200);
using (Pen drw_pen = new Pen(Color.White, 1) )
{
using (GraphicsPath path = new GraphicsPath())
{
path.AddPie(radar_rect, 180, 180);
path.CloseFigure();
using (Region rdRegion = new Region(path) )
{
g.DrawPath(drw_pen, path);
g.Clip = rdRegion;
PictureBox pb = new PictureBox();
pb.Image = (blip);
pb.Size = blip.Size;
g.DrawImage(blip, radar_rect);
}
}
}
}// end paint method
I have tried the DrawImageUnscaled method also but I either get a my tiny picture blown to fill the pie region or nothing is displayed.
Click here to run a sample application that demonstrates the basics of how to do radar (or one way, at least). Note: this application does not do double-buffering or transparency of the tiny image.
Source code for the project is here.
This line:
pb.Image = (blip);
is what's causing the tiny image to appear large. Basically, you pulling a tiny bitmap out of resources, and then setting the PictureBox's Image property to that bitmap (I'm assuming "pb" is a PictureBox on your form). Try commenting out that line and the line below it.

Categories

Resources