I need to pixelize / get points from closed 2d polygon. Not outline but fill it with "pixels" voxels an retrieve their positions as points.
For now I have C# code for line rasteriation, is there any method similar for polygons?
Just draw a polygon using your DrawLine(). There's no specific algorithm for drawing a polygon.
void DrawPoly(IEnumerable<Point> points)
{
endpoints = points.Skip(1).Concat(new []{points.First()});
pairs = points.Zip(endpoints, Tuple.Create);
for(var pair in pairs)
{
DrawLine(pair.Item1, pair.Item2);
}
}
void DrawLine(Point p1, Point p2)
{
// Your Bresenham code here
}
According to the edit, you want a filled polygon. If you wish to manually implement this, try one of these.
It sounds like you want a big list of all the coordinates where the polygon is filled. There is likely a better way to solve the underlying problem. However, you have two options:
Store each point as you draw it. This requires you to rewrite the algorithms mentioned above.
Draw a polygon using an existing library. Then, take the resulting image and attach coordinates to the image matrix, flatten it into a 1D list, and then filter out the non-black values:
/* (0,0), (0,1), ..., (w,h) */
grid = Enumerable.Range(0, width)
.SelectMany(x => Enumerable.Range(0, height)
.Select(y => new Point(x, y)));
flattened = image.SelectMany(p => p)
.Zip(grid, (a,b) => new {PixelValue = a, Coordinate = b});
filledPoints = flattened.Where(p => p.PixelValue == 0)
.Select(p => p.Coordinate);
How can I connect this thing with matrix to stored filled pixels?
public static List<Tuple<int, int>> PixelizePolygon(PaintEventArgs e)
{
List<Tuple<int,int>> pixels = new List<Tuple<int, int>>();
// Create solid brush.
SolidBrush blueBrush = new SolidBrush(Color.Blue);
// Create points that define polygon.
PointF point1 = new PointF(50.0F, 50.0F);
PointF point2 = new PointF(100.0F, 25.0F);
PointF point3 = new PointF(200.0F, 5.0F);
PointF point4 = new PointF(250.0F, 50.0F);
PointF point5 = new PointF(300.0F, 100.0F);
PointF point6 = new PointF(350.0F, 200.0F);
PointF point7 = new PointF(250.0F, 250.0F);
PointF[] curvePoints = { point1, point2, point3, point4, point5, point6, point7 };
// Define fill mode.
FillMode newFillMode = FillMode.Winding;
// Fill polygon to screen.
e.Graphics.FillPolygon(blueBrush, curvePoints, newFillMode);
return pixels;
}
Related
I have two Points I want to connect with an Arc in the C# Graphics Class when my main form is painted. I also have the radius that arc should have and the direction the arc should turn from starting point to the next. I dont see how I should do that with the drawArc overloads provided. Can anybody help me with maybe a function that takes a starting point, a end point, an arc radius and a direction (Clockwise or counter-Clockwise) and then draws that arc? Or Rather I am trying to parse this from how .gcode files specify arc movements and if the radius is negative its a clockwise roation and vice versa.
Am looking forward to some input
I figured it out for you.
Here is the sample code. The key here is the function DrawArcBetweenTwoPoints(). For testing it also draws the center points and the two spokes. You should remove those lines and focus on the DrawArc() part.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.TranslateTransform(ClientSize.Width/2, ClientSize.Height/2);
PointF A = new PointF(0, -40);
PointF B = new PointF(100, 40);
e.Graphics.DrawLine(Pens.DarkBlue, A, B);
DrawPoint(e.Graphics, Brushes.Black, A);
DrawPoint(e.Graphics, Brushes.Black, B);
DrawArcBetweenTwoPoints(e.Graphics, Pens.Red, A, B, 100);
}
public void DrawPoint(Graphics g, Brush brush, PointF A, float size = 8f)
{
g.FillEllipse(brush, A.X-size/2, A.Y-size/2, size, size);
}
public void DrawArcBetweenTwoPoints(Graphics g, Pen pen, PointF a, PointF b, float radius, bool flip = false)
{
if (flip)
{
PointF temp = b;
b =a;
a = temp;
}
// get distance components
double x = b.X-a.X, y = b.Y-a.Y;
// get orientation angle
var θ = Math.Atan2(y, x);
// length between A and B
var l = Math.Sqrt(x*x+y*y);
if (2*radius>=l)
{
// find the sweep angle (actually half the sweep angle)
var φ = Math.Asin(l/(2*radius));
// triangle height from the chord to the center
var h = radius*Math.Cos(φ);
// get center point.
// Use sin(θ)=y/l and cos(θ)=x/l
PointF C = new PointF(
(float)(a.X + x/2 - h*(y/l)),
(float)(a.Y + y/2 + h*(x/l)));
g.DrawLine(Pens.DarkGray, C, a);
g.DrawLine(Pens.DarkGray, C, b);
DrawPoint(g, Brushes.Orange, C);
// Conversion factor between radians and degrees
const double to_deg = 180/Math.PI;
// Draw arc based on square around center and start/sweep angles
g.DrawArc(pen, C.X-radius, C.Y-radius, 2*radius, 2*radius,
(float)((θ-φ)*to_deg)-90, (float)(2*φ*to_deg));
}
}
private void Form1_Resize(object sender, EventArgs e)
{
this.Refresh();
}
}
I tested the flip arc with the following code
DrawArcBetweenTwoPoints(e.Graphics, Pens.Red, A, B, 100);
DrawArcBetweenTwoPoints(e.Graphics, Pens.Red, A, B, 100, true);
Welcome to Stackoverflow!
I recommend using a Bezier Curve.
An implementation is shown below which you can adapt for your arc.
The BezierCurve (check Microsoft documentation) is a curve described fully by 4 points.
The start/end points should be clear.
The control points define the curvature of the arc.
You will need to calculate the control points yourself. I recommend trying Code Sample 1 to see how it functions and then adapting the second code sample for your specific purposes. Change the values of the ComputedRotationAngleA/B, flip them around. Change the ControlPointDistance amount. You should be able to get a good idea of how the C# implentation of it works.
Then calculate your own values and plug them into the Code provided in Code Sample 2.
Note:
ComputedRotationAngleA/B control the direction of the arc.
ControlPointDistance controls the point where the curvature is added to the arc.
Good luck!
Code Sample 1:
internal void DrawBezierCurveTest(Pen pen, PaintEventArgs e)
{
int ComputedRotationAngleA = 20, ComputedRotationAngleB = -20;
int ControlPointDistance = 5;
LinePath = new GraphicsPath();
var p1 = new Point(StartX, StartY);
var p2 = new Point(StartX + ControlPointDistance , StartY + ControlPointDistance );
p2.Offset(ComputedRotationAngleA, ComputedRotationAngleA);
var p3 = new Point(EndX - ControlPointDistance , EndY - ControlPointDistance );
p3.Offset(ComputedRotationAngleB, ComputedRotationAngleB);
var p4 = new Point(EndX , EndY);
LinePath.AddBezier(p1, p2, p3, p4);
e.Graphics.DrawPath(pen, LinePath);
}
Code Sample 2:
internal void DrawBezierCurve(Pen pen, PaintEventArgs e)
{
LinePath = new GraphicsPath();
var p1 = new Point(StartX, StartY); //starting point
var p2 = new Point(FirstDipX, FirstDipY) //first control point
var p3 = new Point(SecondDipX, SecondDipY); //second control point
var p4 = new Point(EndX, EndY); //ending point
LinePath.AddBezier(p1, p2, p3, p4);
e.Graphics.DrawPath(pen, LinePath);
}
How do I use Graphics.DrawCurve to draw an EaseInOutCubic curve in .NET?
Link to visual of the curve I'm talking about
http://easings.net/#easeInOutCubic
I know how to draw a simple curve but I don't understand how to specifically draw that type of curve. How do i go about achieving this?
This is the equation for the curve in JS which is trivial to conver to C#.
// t: current time, b: begInnIng value, c: change In value, d: duration
easeInOutCubic: function (x, t, b, c, d) {
if ((t/=d/2) < 1) return c/2*t*t*t + b;
return c/2*((t-=2)*t*t + 2) + b;
},
Sample C# to draw curve:
private void DrawCurvePoint(PaintEventArgs e)
{
// Create pens.
Pen redPen = new Pen(Color.Red, 3);
Pen greenPen = new Pen(Color.Green, 3);
// Create points that define curve.
Point point1 = new Point(50, 50);
Point point2 = new Point(100, 25);
Point point3 = new Point(200, 5);
Point point4 = new Point(250, 50);
Point point5 = new Point(300, 100);
Point point6 = new Point(350, 200);
Point point7 = new Point(250, 250);
Point[] curvePoints = {point1, point2, point3, point4, point5, point6, point7};
// Draw lines between original points to screen.
e.Graphics.DrawLines(redPen, curvePoints);
// Draw curve to screen.
e.Graphics.DrawCurve(greenPen, curvePoints);
}
I have several points, and I try to draw Bezier curve using code below
PathFigure pf = new PathFigure(points.From, ps, false); //ps - list of Bezier segments
PathFigureCollection pfc = new PathFigureCollection();
pfc.Add(pf);
var pge = new PathGeometry();
pge.Figures = pfc;
Path p = new Path();
p.Data = pge;
p.Stroke = new SolidColorBrush(Color.FromRgb(244, 111, 011));
My Bezier segments look like this
1,2,3 points - first segment
3,4,5 points - second
5,6,7.. ..
But I got this strange curve (here is 3 big (Nodes) and 7 small ellipse (is my points)):
The line you're getting is the union of three distinct Bezier curves - one for each group of three points. (One for each "Bezier segment"?)
If you want a single smooth curve, you need to pass your 9 (or more) points as a single collection of points (single "Bezier segment"?), not as groups of three points.
Edit: Apparently BezierSegment only supports three points, so no wonder this doesn't work. Even 'PolyBezierSegment' just gives a collection of Bezier segments rather than a single smooth Bezier...
So since WPF doesn't give you anything useful, I knocked something together using the maths here. It's a numeric solution, but it seems to be pretty performant even with enough points to look nice and smooth:
PolyLineSegment GetBezierApproximation(Point[] controlPoints, int outputSegmentCount)
{
Point[] points = new Point[outputSegmentCount + 1];
for (int i = 0; i <= outputSegmentCount; i++)
{
double t = (double)i / outputSegmentCount;
points[i] = GetBezierPoint(t, controlPoints, 0, controlPoints.Length);
}
return new PolyLineSegment(points, true);
}
Point GetBezierPoint(double t, Point[] controlPoints, int index, int count)
{
if (count == 1)
return controlPoints[index];
var P0 = GetBezierPoint(t, controlPoints, index, count - 1);
var P1 = GetBezierPoint(t, controlPoints, index + 1, count - 1);
return new Point((1 - t) * P0.X + t * P1.X, (1 - t) * P0.Y + t * P1.Y);
}
Using this,
private void Grid_Loaded(object sender, RoutedEventArgs e)
{
Point[] points = new[] {
new Point(0, 200),
new Point(0, 0),
new Point(300, 0),
new Point(350, 200),
new Point(400, 0)
};
var b = GetBezierApproximation(points, 256);
PathFigure pf = new PathFigure(b.Points[0], new[] { b }, false);
PathFigureCollection pfc = new PathFigureCollection();
pfc.Add(pf);
var pge = new PathGeometry();
pge.Figures = pfc;
Path p = new Path();
p.Data = pge;
p.Stroke = new SolidColorBrush(Color.FromRgb(255, 0, 0));
((Grid)sender).Children.Add(p);
}
gives
Since each of your curves has one control point (a point that influences the curve but isn't necessarily on the curve), you're using quadratic Bézier curves.
If you want to draw two quadratic curves that share an endpoint, and you want the joint to appear smooth, the control points on each side of the shared endpoint must be collinear with the endpoint. That is, the two control points and the endpoint between them must all lie on a straight line. Example:
The solid black discs are the endpoints. The hollow circles are the control points. The solid black line is the curve. The dotted lines show that each endpoint is collinear (on a straight line with) the control point on either side.
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
I have created a c# windows application and written 75% of the code. The program allows the user to create a flow chart, and will shade the flow chart shapes according to their status. I wanted them to become 3d buttons such as from the website Webdesign.org
Rather than create a PNG for each button, I wanted to create them in C# using brushes or other technique, such as:
// Create solid brush.
SolidBrush blueBrush = new SolidBrush(Color.Blue);
// Create points that define polygon.
PointF point1 = new PointF(50.0F, 50.0F);
PointF point2 = new PointF(100.0F, 25.0F);
PointF point3 = new PointF(200.0F, 5.0F);
PointF point4 = new PointF(250.0F, 50.0F);
PointF point5 = new PointF(300.0F, 100.0F);
PointF point6 = new PointF(350.0F, 200.0F);
PointF point7 = new PointF(250.0F, 250.0F);
PointF[] curvePoints = {point1, point2, point3, point4, point5, point6, point7};
// Define fill mode.
FillMode newFillMode = FillMode.Winding;
// Fill polygon to screen.
e.Graphics.FillPolygon(blueBrush, curvePoints, newFillMode);
I know WPF has radial gradients, but can I do something simular in CGI?
Unlike WPF, GDI+/WinForms does not have a RadialGradientBrush. However you can achieve the same effect using a PathGradientBrush.
Here's an example:
Rectangle bounds = ...;
using (var ellipsePath = new GraphicsPath())
{
ellipsePath.AddEllipse(bounds);
using (var brush = new PathGradientBrush(ellipsePath))
{
brush.CenterPoint = new PointF(bounds.Width/2f, bounds.Height/2f);
brush.CenterColor = Color.White;
brush.SurroundColors = new[] { Color.Red };
brush.FocusScales = new PointF(0, 0);
e.Graphics.FillRectangle(brush, bounds);
}
}
The PathGradientBrush has lots of properties to experiment with to ensure you get the effect you're after.
Take a look at this Codeproject article, and then at the Path gradient.