I am drawing a line on a control on my Windows form like this:
// Get Graphics object from chart
Graphics graph = e.ChartGraphics.Graphics;
PointF point1 = PointF.Empty;
PointF point2 = PointF.Empty;
// Set Maximum and minimum points
point1.X = -110;
point1.Y = -110;
point2.X = 122;
point2.Y = 122;
// Convert relative coordinates to absolute coordinates.
point1 = e.ChartGraphics.GetAbsolutePoint(point1);
point2 = e.ChartGraphics.GetAbsolutePoint(point2);
// Draw connection line
graph.DrawLine(new Pen(Color.Yellow, 3), point1, point2);
I would like to know if it is possible to draw a dashed (dotted) line instead of a regular solid line?
It's pretty simple once you figure out the formatting that defines the dashes:
float[] dashValues = { 5, 2, 15, 4 };
Pen blackPen = new Pen(Color.Black, 5);
blackPen.DashPattern = dashValues;
e.Graphics.DrawLine(blackPen, new Point(5, 5), new Point(405, 5));
The numbers in the float array represent dash lengths of different colors. So for a simple dash of 2 pixels on (black) and two off each your aray would look like: {2,2} The pattern then repeats. If you wanted 5-wide dashes with a space of 2 pixels you would use {5,2}
In your code it would look like:
// Get Graphics object from chart
Graphics graph = e.ChartGraphics.Graphics;
PointF point1 = PointF.Empty;
PointF point2 = PointF.Empty;
// Set Maximum and minimum points
point1.X = -110;
point1.Y = -110;
point2.X = 122;
point2.Y = 122;
// Convert relative coordinates to absolute coordinates.
point1 = e.ChartGraphics.GetAbsolutePoint(point1);
point2 = e.ChartGraphics.GetAbsolutePoint(point2);
// Draw (dashed) connection line
float[] dashValues = { 4, 2 };
Pen dashPen= new Pen(Color.Yellow, 3);
dashPen.DashPattern = dashValues;
graph.DrawLine(dashPen, point1, point2);
I think you can accomplish this by changing the pen you use to draw your line.
So, replace the last 2 lines in your example with:
var pen = new Pen(Color.Yellow, 3);
pen.DashStyle = DashStyle.Dash;
graph.DrawLine(pen, point1, point2);
Pen has a public property that is defined as
public DashStyle DashStyle { get; set; }
you can set DasStyle.Dash if you want to draw a Dashed line.
Pen.DashPattern will do this. Look here for an example
In more modern C#:
var dottedPen = new Pen(Color.Gray, width: 1) { DashPattern = new[] { 1f, 1f } };
To answer this question regarding the generation of a dashed line using the code-behind:
Pen dashPenTest = new(Brushes.DodgerBlue, 1);
Line testLine = new()
{
Stroke = dashPenTest.Brush, //Brushes.Aqua,
StrokeThickness = dashPenTest.Thickness,//1,
StrokeDashArray = new DoubleCollection() { 8,4 },
X1 = 0,
X2 = canvas.Width,
Y1 = 10,
Y2 = 10
};
canvas.Children.Add(testLine);
This answer make use of the generation of a canvas in the xaml:
<Canvas x:Name ="canvas" Background="White" Height="300" Width="300">
The important method here is the "StrokeDashArray" that generates the dashes for the line drawn. More information is given here: Shape.StrokeDashArray
Related
I am having three image of pan card for testing skew of image using emgucv and c#.
1st image which is on top Detected 180 degree working properly.
2nd image which is in middle Detected 90 dgree should detected as 180 degree.
3rd image Detected 180 degree should detected as 90 degree.
One observation I am having that i wanted to share here is when i crop unwanted part of image from up and down side of pan card using paint brush, it gives me expected result using below mention code.
Now i wanted to understand how i can remove the unwanted part using programming.
I have played with contour and roi but I am not able to figure out how to fit the same. I am not able to understand whether emgucv itself selects contour or I have to do something.
Please suggest any suitable code example.
Please check code below for angle detection and please help me. Thanks in advance.
imgInput = new Image<Bgr, byte>(impath);
Image<Gray, Byte> img2 = imgInput.Convert<Gray, Byte>();
Bitmap imgs;
Image<Gray, byte> imgout = imgInput.Convert<Gray, byte>().Not().ThresholdBinary(new Gray(50), new Gray(125));
VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();
Emgu.CV.Mat hier = new Emgu.CV.Mat();
var blurredImage = imgInput.SmoothGaussian(5, 5, 0 , 0);
CvInvoke.AdaptiveThreshold(imgout, imgout, 255, Emgu.CV.CvEnum.AdaptiveThresholdType.GaussianC, Emgu.CV.CvEnum.ThresholdType.Binary, 5, 45);
CvInvoke.FindContours(imgout, contours, hier, Emgu.CV.CvEnum.RetrType.External, Emgu.CV.CvEnum.ChainApproxMethod.ChainApproxSimple);
if (contours.Size >= 1)
{
for (int i = 0; i <= contours.Size; i++)
{
Rectangle rect = CvInvoke.BoundingRectangle(contours[i]);
RotatedRect box = CvInvoke.MinAreaRect(contours[i]);
PointF[] Vertices = box.GetVertices();
PointF point = box.Center;
PointF edge1 = new PointF(Vertices[1].X - Vertices[0].X, Vertices[1].Y - Vertices[0].Y);
PointF edge2 = new PointF(Vertices[2].X - Vertices[1].X, Vertices[2].Y - Vertices[1].Y);
double r = edge1.X + edge1.Y;
double edge1Magnitude = Math.Sqrt(Math.Pow(edge1.X, 2) + Math.Pow(edge1.Y, 2));
double edge2Magnitude = Math.Sqrt(Math.Pow(edge2.X, 2) + Math.Pow(edge2.Y, 2));
PointF primaryEdge = edge1Magnitude > edge2Magnitude ? edge1 : edge2;
double primaryMagnitude = edge1Magnitude > edge2Magnitude ? edge1Magnitude : edge2Magnitude;
PointF reference = new PointF(1, 0);
double refMagnitude = 1;
double thetaRads = Math.Acos(((primaryEdge.X * reference.X) + (primaryEdge.Y * reference.Y)) / (primaryMagnitude * refMagnitude));
double thetaDeg = thetaRads * 180 / Math.PI;
imgInput = imgInput.Rotate(thetaDeg, new Bgr());
imgout = imgout.Rotate(box.Angle, new Gray());
Bitmap bmp = imgout.Bitmap;
break;
}
}
The Problem
Let us start with the problem before the solution:
Your Code
When you submit code, asking for help, at least make some effort to "clean" it. Help people help you! There's so many lines of code here that do nothing. You declare variables that are never used. Add some comments that let people know what it is that you think your code should do.
Bitmap imgs;
var blurredImage = imgInput.SmoothGaussian(5, 5, 0, 0);
Rectangle rect = CvInvoke.BoundingRectangle(contours[i]);
PointF point = box.Center;
double r = edge1.X + edge1.Y;
// Etc
Adaptive Thresholding
The following line of code produces the following images:
CvInvoke.AdaptiveThreshold(imgout, imgout, 255, Emgu.CV.CvEnum.AdaptiveThresholdType.GaussianC, Emgu.CV.CvEnum.ThresholdType.Binary, 5, 45);
Image 1
Image 2
Image 3
Clearly this is not what you're aiming for since the primary contour, the card edge, is completely lost. As a tip, you can always use the following code to display images at runtime to help you with debugging.
CvInvoke.NamedWindow("Output");
CvInvoke.Imshow("Output", imgout);
CvInvoke.WaitKey();
The Soltuion
Since your in example images the card is primarily a similar Value (in the HSV sense) to the background. I do not think simple gray scale thresholding is the correct approach in this case. I purpose the following:
Algorithm
Use Canny Edge Detection to extract the edges in the image.
Dilate the edges so as the card content combines.
Use Contour Detection to filter for the combined edges with the largest bounding.
Fit this primary contour with a rotated rectangle in order to extract the corner points.
Use the corner points to define a transformation matrix to be applied using WarpAffine.
Warp and crop the image.
The Code
You may wish to experiment with the parameters of the Canny Detection and Dilation.
// Working Images
Image<Bgr, byte> imgInput = new Image<Bgr, byte>("Test1.jpg");
Image<Gray, byte> imgEdges = new Image<Gray, byte>(imgInput.Size);
Image<Gray, byte> imgDilatedEdges = new Image<Gray, byte>(imgInput.Size);
Image<Bgr, byte> imgOutput;
// 1. Edge Detection
CvInvoke.Canny(imgInput, imgEdges, 25, 80);
// 2. Dilation
CvInvoke.Dilate(
imgEdges,
imgDilatedEdges,
CvInvoke.GetStructuringElement(
ElementShape.Rectangle,
new Size(3, 3),
new Point(-1, -1)),
new Point(-1, -1),
5,
BorderType.Default,
new MCvScalar(0));
// 3. Contours Detection
VectorOfVectorOfPoint inputContours = new VectorOfVectorOfPoint();
Mat hierarchy = new Mat();
CvInvoke.FindContours(
imgDilatedEdges,
inputContours,
hierarchy,
RetrType.External,
ChainApproxMethod.ChainApproxSimple);
VectorOfPoint primaryContour = (from contour in inputContours.ToList()
orderby contour.GetArea() descending
select contour).FirstOrDefault();
// 4. Corner Point Extraction
RotatedRect bounding = CvInvoke.MinAreaRect(primaryContour);
PointF topLeft = (from point in bounding.GetVertices()
orderby Math.Sqrt(Math.Pow(point.X, 2) + Math.Pow(point.Y, 2))
select point).FirstOrDefault();
PointF topRight = (from point in bounding.GetVertices()
orderby Math.Sqrt(Math.Pow(imgInput.Width - point.X, 2) + Math.Pow(point.Y, 2))
select point).FirstOrDefault();
PointF botLeft = (from point in bounding.GetVertices()
orderby Math.Sqrt(Math.Pow(point.X, 2) + Math.Pow(imgInput.Height - point.Y, 2))
select point).FirstOrDefault();
PointF botRight = (from point in bounding.GetVertices()
orderby Math.Sqrt(Math.Pow(imgInput.Width - point.X, 2) + Math.Pow(imgInput.Height - point.Y, 2))
select point).FirstOrDefault();
double boundingWidth = Math.Sqrt(Math.Pow(topRight.X - topLeft.X, 2) + Math.Pow(topRight.Y - topLeft.Y, 2));
double boundingHeight = Math.Sqrt(Math.Pow(botLeft.X - topLeft.X, 2) + Math.Pow(botLeft.Y - topLeft.Y, 2));
bool isLandscape = boundingWidth > boundingHeight;
// 5. Define warp crieria as triangles
PointF[] srcTriangle = new PointF[3];
PointF[] dstTriangle = new PointF[3];
Rectangle ROI;
if (isLandscape)
{
srcTriangle[0] = botLeft;
srcTriangle[1] = topLeft;
srcTriangle[2] = topRight;
dstTriangle[0] = new PointF(0, (float)boundingHeight);
dstTriangle[1] = new PointF(0, 0);
dstTriangle[2] = new PointF((float)boundingWidth, 0);
ROI = new Rectangle(0, 0, (int)boundingWidth, (int)boundingHeight);
}
else
{
srcTriangle[0] = topLeft;
srcTriangle[1] = topRight;
srcTriangle[2] = botRight;
dstTriangle[0] = new PointF(0, (float)boundingWidth);
dstTriangle[1] = new PointF(0, 0);
dstTriangle[2] = new PointF((float)boundingHeight, 0);
ROI = new Rectangle(0, 0, (int)boundingHeight, (int)boundingWidth);
}
Mat warpMat = new Mat(2, 3, DepthType.Cv32F, 1);
warpMat = CvInvoke.GetAffineTransform(srcTriangle, dstTriangle);
// 6. Apply the warp and crop
CvInvoke.WarpAffine(imgInput, imgInput, warpMat, imgInput.Size);
imgOutput = imgInput.Copy(ROI);
imgOutput.Save("Output1.bmp");
Two extension methods are used:
static List<VectorOfPoint> ToList(this VectorOfVectorOfPoint vectorOfVectorOfPoint)
{
List<VectorOfPoint> result = new List<VectorOfPoint>();
for (int contour = 0; contour < vectorOfVectorOfPoint.Size; contour++)
{
result.Add(vectorOfVectorOfPoint[contour]);
}
return result;
}
static double GetArea(this VectorOfPoint contour)
{
RotatedRect bounding = CvInvoke.MinAreaRect(contour);
return bounding.Size.Width * bounding.Size.Height;
}
Outputs
Meta Example
Back here. Is there any way to improve the quality of the Arc?
I'm using e.Graphics.SmoothingMode = SmoothingMode.AntiAlias
This is the piece of code that creates the arc:
using (GraphicsPath gp = new GraphicsPath())
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
gp.Reset();
gp.AddPie(_OuterRectangle, (float)_Properties.Origin, (float)_Properties.GaugeType);
gp.Reverse();
gp.AddPie(_InnerRectangle, (float)_Properties.Origin, (float)_Properties.GaugeType);
gp.Reverse();
pArea.SetClip(gp);
using (Pen oPen = new Pen(this.ForeColor, 2f))
{
e.Graphics.DrawPath(oPen, gp);
}
e.Graphics.SetClip(ClientRectangle);
}
Thanks in advance.
EDIT:
I've did what LarsTech proposed and now the quality is perfect, but I'm not having the figure I need:
OuterRectangle: is the ClientRectangle area, that I'm manipulating it to make Width and Height the same lenght;
InnerRectangle: is 2/3ths of the ClientRectangle area, ergo, of the OuterRectangle;
Properties.Origin: is the angle where the arc starts. I have it in an enumerator as Cardinal Points, where North is 270, East is 0,
and so. In case of the figure, is SouthWest, 135 degrees;
Properties.GaugeType: is another enumerator that says if is Complete = 360, Half = 180, Quarter = 90, so with that I can determine the sweep angle. In case of the figure is ThreeQuarter, 270 degrees.
The problem:
When clipping a region of the current Graphics (Graphics.SetClip method), the resulting drawing loses quality, because the antialiasing effect generated by Graphics.SmoothingMode = SmoothingMode.AntiAlias is lost.
A possible solution is to avoid clipping the region defined by the GraphicsPath used to design the arcs (GraphicsPath.AddPie method); this, however, leaves the lines of the Pie visible, compromising the shape.
Another solution is to draw an ellipsis in the center of the arcs using the background color of the Canvas. Since the arcs are drawn using two rectangles, we can use the inner rectagle, inflate it (Rectangle.Inflate method) as needed (a fraction - Pen.Width / 2 - of the Pen size used for the ouline, usually).
This allows to delete the artifacts generated by the GraphicsPath shapes and to draw some other graphics content in the center of the shapes.
For example, using different Brushes:
LinearGradientBrush HatchBrush TextureBrush
Of course there are other methods to achieve the same result. We could draw the Arcs using the GraphicsPath.AddArc method, extract or calculate the first and last points of the Arcs and use them to draw two lines (GraphicsPath.AddLine) that will close the figures.
But, since we want to draw different graphics objects in the center of the arcs, these objects will cover the center area anyway.
How to use this code:
In a Form, add a TrackBar (named tbarSpeed, here)
Add a PictureBox (named Canvas), with Size (200, 200).
Wire up the TrackBar tbarSpeed_Scroll event and the Panel Canvas_Paint event.
using System.Drawing;
using System.Drawing.Drawing2D;
float GaugeValue = 88.0f;
float GaugeSweepAngle = 270.0f;
float GaugeStartAngle = 135.0F;
private void Canvas_Paint(object sender, PaintEventArgs e)
{
var canvas = sender as Control;
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
var outerRectangle = new Rectangle(10, 10, 180, 180);
var innerRectangle = new Rectangle(30, 30, 140, 140);
var blendRectangle = new Rectangle(10, 10, 180, 160);
var innerCenter = new PointF(outerRectangle.Left + (outerRectangle.Width / 2),
outerRectangle.Top + (outerRectangle.Height / 2));
float gaugeLength = (outerRectangle.Width / 2) - 2;
using (var path = new GraphicsPath())
{
path.AddPie(outerRectangle, GaugeStartAngle, GaugeSweepAngle);
path.AddPie(innerRectangle, GaugeStartAngle, GaugeSweepAngle);
innerRectangle.Inflate(-1, -1);
using (var pen = new Pen(Color.White, 3f))
using (var backgroundbrush = new SolidBrush(canvas.BackColor))
using (var gradientBrush = new LinearGradientBrush(blendRectangle,
Color.Green, Color.Red, LinearGradientMode.ForwardDiagonal))
{
var blend = new Blend()
{
Factors = new[] { 0.0f, 0.0f, 0.1f, 0.3f, 0.7f, 1.0f },
Positions = new[] { 0.0f, 0.2f, 0.4f, 0.6f, 0.8f, 1.0f }
};
gradientBrush.Blend = blend;
e.Graphics.FillPath(gradientBrush, path);
e.Graphics.DrawPath(pen, path);
e.Graphics.FillEllipse(backgroundbrush, innerRectangle);
using (var format = new StringFormat())
{
format.Alignment = StringAlignment.Center;
format.LineAlignment = StringAlignment.Center;
innerRectangle.Location = new Point(innerRectangle.X, innerRectangle.Y + canvas.Font.Height);
e.Graphics.DrawString(GaugeValue.ToString() + "%", canvas.Font, Brushes.White, innerRectangle, format);
}
using (var mx = new Matrix())
{
mx.RotateAt(GaugeStartAngle + 90 + (GaugeValue * (GaugeSweepAngle / 100)), innerCenter);
e.Graphics.Transform = mx;
e.Graphics.DrawLine(pen, innerCenter, new PointF(innerCenter.X, innerCenter.Y - gaugeLength));
e.Graphics.ResetTransform();
}
}
}
}
private void tbarSpeed_Scroll(object sender, EventArgs e)
{
GaugeValue = tbarSpeed.Value;
Canvas.Invalidate();
}
Sample code on PasteBin
So I have a List<object> of longitude and latitude coordinates of two points, and I need to connect the line between them. The trick is to display all of the lines within a panel so that they are scaled within the panel's dimensions (converting coordinate numbers to match the pixels) and I almost got it. However I'm confounded by some unknown problem. The code is:
int canvasWidth = panel1.Width,
canvasHeight = panel1.Height;
var minX1 = tockeKoordinate.Min(x => x.startX);
var minX2 = tockeKoordinate.Min(x => x.endX);
var minX = Math.Min(minX1, minX2);
var maxX1 = tockeKoordinate.Max(x => x.startX);
var maxX2 = tockeKoordinate.Max(x => x.endX);
var maxX = Math.Max(maxX1, maxX2);
var maxY1 = tockeKoordinate.Max(x => x.startY);
var maxY2 = tockeKoordinate.Max(x => x.endY);
var maxY = Math.Max(maxY1, maxY2);
var minY1 = tockeKoordinate.Min(x => x.startY);
var minY2 = tockeKoordinate.Min(x => x.endY);
var minY = Math.Min(minY1, minY2);
double coordinatesWidth = Math.Abs(maxX - minX),
coordinatesHeight = Math.Abs(maxY - minY);
float coefWidth = (float)coordinatesWidth / canvasWidth,
coefHeight = (float)coordinatesHeight / canvasHeight;
Basically I check the List for minimum and maximum XY coordinates, so I know what the extreme values are. Then I use a coeficient value to recalculate the coords in pixels so that are within the panel. When I use this:
drawLine(Math.Abs((float)(line.startX - minX) / coefWidth),
Math.Abs((float)(line.startY - minY) / coefHeight),
Math.Abs((float)(line.endX - maxX) / coefWidth),
Math.Abs((float)(line.endY - maxY) / coefHeight));
which is in foreach loop that iterates trough all the elements from the List . The drawline() method is as follows:
private void drawLine(float startX, float startY, float endX, float endY)
{
PointF[] points =
{
new PointF(startX, startY),
new PointF(endX, endY),
};
g.DrawLine(myPen, points[0], points[1]);
}
WHen all of this is put together, I get this picture:
I know for a fact that the "lines" should be connected and form shapes, in this case they represent roads in a suburban area.
I figured that it treats every coordinate set like it is the only one and then scales it to the panel dimensions. Actually it should scale it in reference to all of the other coordinates
It should "zoom" them out and connect with each other, because that is the way I defined the panel dimensions and everything else.
EDIT: ToW's solution did the trick, with this line of code changed to use my List:
foreach (var line in tockeKoordinate)
{
gp.AddLine((float)(line.startX), (float)(line.startY), (float)(line.endX), (float)(line.endY));
gp.CloseFigure();
}
End result when working properly:
As far as I can see your best bet would be to add all those lines to a GraphicsPath.
After it is complete you can look at its bounding rectangle and compare it to the size your Panel offers.
Then you can calculate a scale for the Graphics object to draw with and also a translation.
Finally you draw the lines with Graphics.DrawPath.
All with just 2 division on your side :-)
Here is an example:
private void panel1_Paint(object sender, PaintEventArgs e)
{
Graphics G = e.Graphics;
Random R = new Random(13);
GraphicsPath gp = new GraphicsPath();
for (int i = 0; i < 23; i++)
{
gp.AddLine(R.Next(1234), R.Next(1234), R.Next(1234), R.Next(1234));
gp.CloseFigure(); // disconnect lines
}
RectangleF rect = gp.GetBounds();
float scale = Math.Min(1f * panel1.Width / rect.Width,
1f * panel1.Height / rect.Height);
using (Pen penUnscaled = new Pen(Color.Blue, 4f))
using (Pen penScaled = new Pen(Color.Red, 4f))
{
G.Clear(Color.White);
G.DrawPath(penUnscaled, gp);
G.ScaleTransform(scale, scale);
G.TranslateTransform(-rect.X, -rect.Y);
G.DrawPath(penScaled, gp);
}
}
A few notes:
The blue lines do not fit onto the panel
The red lines are scaled down to fit
The Pen is scaled along with the rest of the Graphics but won't go under 1f.
To create connected lines do add a PointF[] or, more convenient a List<PointF>.ToArray().
I really should have used panel1.ClientSize.Width instead of panel1.Width etc..; now it is off a tiny bit at the bottom; bad boy me ;-)
Ok so i have created a triangle but I cant for the life of me work out the coordinates to create a simple hexagon,
Point[] shape = new Point[3];
shape[0] = new Point(200, 100);
shape[1] = new Point(300, 200);
shape[2] = new Point(100, 200);
This makes a triangle but I cant figure out the x and y values for a hexagon, sounds like a simple question but my brain just isn't working correctly today, Below is the array for the hexagon I just can't figure out the values.
Point[] shape = new Point[6];
shape[0] = new Point(0, 0);
shape[1] = new Point(0, 0);
shape[2] = new Point(0, 0);
shape[3] = new Point(0, 0);
shape[4] = new Point(0, 0);
shape[5] = new Point(0, 0);
Any help would be great thanks!
Since I've already written a comment, I guess I should demonstrate that in some real code.
I created a WinForms application with a Panel object on which I can draw. Then I've overridden the Paint event on that to draw me a hexagon.
private void panel1_Paint(object sender, PaintEventArgs e)
{
var graphics = e.Graphics;
//Get the middle of the panel
var x_0 = panel1.Width / 2;
var y_0 = panel1.Height / 2;
var shape = new PointF[6];
var r = 70; //70 px radius
//Create 6 points
for(int a=0; a < 6; a++)
{
shape[a] = new PointF(
x_0 + r * (float)Math.Cos(a * 60 * Math.PI / 180f),
y_0 + r * (float)Math.Sin(a * 60 * Math.PI / 180f));
}
graphics.DrawPolygon(Pens.Red, shape);
}
This then draws
As I said, the key is to view the hexagon as a "discrete" circle. The points are all computed as being on the outer part of a perfect circle, which are then connected with a straight line. You can create all regular n-Point shapes with this technique (a pentagon e.g. as a 5-regular shape ;))
So, you just "inscribe" the 6 points in the circle to get your hexagon, as shown in this diagram with a regular 5-point shape:
Then remember that you can compute the (x,y) coordinates of a point given its polar coordinates (r, phi) as
To which you can also add an offset , which is in my case the center of the frame I'm drawing in.
I wanted to draw thick lines using Graphics.Lines() method. But it looks like the API has some bugs. If you try to render the user control with the following code, you would get weird looking image. I was wondering if there is some smoothing mode or something similar that could take care of this line drawing glitch.
private void UserControl1_Paint(object sender, PaintEventArgs e)
{
int n = 100;
Point[] points = new Point[n];
double x = 2;
int y = 50;
for (int i = 0; i < n; i++)
{
Point p = new Point();
p.X = 200 + (int)(i * x);
p.Y = 200 + (int)(Math.Sin(i * 0.2) * y);
points[i] = p;
}
Pen pen = new Pen(new SolidBrush(Color.Blue));
//Pen pen = new Pen(new LinearGradientBrush(new Point(0, 0), new Point(0, 100), Color.Black, Color.Red));
pen.Width = 200;
e.Graphics.DrawLines(pen, points);
}
You see the effect of GDI+ trying to draw end-caps on the line. That's not going to come to a good end with such a thick pen. About what you'd imagine from daVinci painting the Mona Lisa with a broom. Fix:
Pen pen = new Pen(new SolidBrush(Color.Blue));
pen.EndCap = System.Drawing.Drawing2D.LineCap.Square;
pen.StartCap = System.Drawing.Drawing2D.LineCap.Square;
Or draw a polygon instead so that GDI+ has a better idea what is front and back:
e.Graphics.DrawPolygon(pen, points);
Well, it doesn't look like a devil anymore. Keep the line width proportional to the details in the line.
Here is the result of your code drawing using a pen of width 200 (pixels):
And here it is at a width of 2:
The pen width property is usually pixels, but it is based on the Graphics object's PageUnit property (itself a GraphicsUnit property). Check to make sure you've set these values to what you want.