Title: Fill a shape with random lines in C#
Click and drag to draw a shape. When you release the mouse, the program closes the shape and fills it with random line segments.
The following code shows how the program lets you draw a shape.
// The points selected by the user.
private List>Point> ShapePoints = new List<Point>();
private GraphicsPath ShapePath = null;
private GraphicsPath LinesPath = null;
private bool IsDrawing = false;
// Start drawing.
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
ShapePoints = new List<Point>();
ShapePoints.Add(e.Location);
IsDrawing = true;
Refresh();
}
// Continue drawing.
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (!IsDrawing) return;
ShapePoints.Add(e.Location);
Refresh();
this.DoubleBuffered = true;
}
// Finish drawing.
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
if (!IsDrawing) return;
IsDrawing = false;
// Generate the random lines to fill the shape.
GenerateLines();
Refresh();
}
The MouseDown event handler starts the process. It saves the first point in the ShapePoints list and sets IsDrawing to true.
The MouseMove event handler adds a new point to ShapePoints if IsDrawing is true.
If IsDrawing is true, the MouseUp event handler sets IsDrawing to false and then calls GenerateLines to generate the line segments. The following code shows how GenerateLines works.
// Generate the random lines to fill the shape.
private void GenerateLines()
{
if (ShapePoints.Count < 3)
{
ShapePath = null;
LinesPath = null;
return;
}
// Make the shape's path.
ShapePath = new GraphicsPath();
ShapePath.AddPolygon(ShapePoints.ToArray());
// Get the shape's bounds.
RectangleF bounds = ShapePath.GetBounds();
int xmin = (int)(bounds.Left);
int xmax = (int)(bounds.Right) + 1;
int ymin = (int)(bounds.Top);
int ymax = (int)(bounds.Bottom) + 1;
// Generate random lines.
LinesPath = new GraphicsPath();
int num_lines = (int)((bounds.Width + bounds.Height) / 8);
Random rand = new Random();
int x1, y1, x2, y2;
for (int i = 1; i <= num_lines / 2; i++)
{
x1 = rand.Next(xmin, xmax);
y1 = ymin;
x2 = rand.Next(xmin, xmax);
y2 = ymax;
LinesPath.AddLine(x1, y1, x2, y2);
x1 = xmin;
y1 = rand.Next(ymin, ymax);
x2 = xmax;
y2 = rand.Next(ymin, ymax);
LinesPath.AddLine(x1, y1, x2, y2);
}
}
If the user has not selected at least 3 points, then the GenerateLines method cannot draw a shape so it simply exits.
Otherwise it creates a GraphicsPath object and adds a shape that connects the selected points.
Next the method creates a second GraphicsPath to hold the line segments. It gets the shape's dimensions and uses them to calculate a number of line segments to draw. It then generates the desired number of lines (approximately), half oriented vertically and half oriented horizontally, and adds them to the line segment GraphicsPath.
Finally, the following code shows how the program draws the shape.
// Draw the shape.
private void Form1_Paint(object sender, PaintEventArgs e)
{
// Draw the shape.
if (IsDrawing)
{
// Draw the lines so far.
if (ShapePoints.Count > 1)
{
e.Graphics.DrawLines(Pens.Green, ShapePoints.ToArray());
}
}
else
{
// Fill and outline the finished shape.
if (ShapePath != null)
{
if (chkAntiAlias.Checked)
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.FillPath(Brushes.LightGreen, ShapePath);
e.Graphics.DrawPath(Pens.Green, ShapePath);
// Fill with the lines.
e.Graphics.SetClip(ShapePath);
e.Graphics.DrawPath(Pens.Green, LinesPath);
}
}
}
If the user is currently drawing, the program draws the shape so far as a series of line segments.
If the user is not currently drawing, the program draws the shape. First it checks the Anti Alias CheckBox. If the box is checked, the program sets the Graphics object's SmoothingMode property to make the resulting drawing smoother.
The code then fills the shape's path and outlines it. Next it sets the Graphics object's clipping region to the path the user drew. It finishes by drawing the lines, which are clipped by the clipping region.
Unfortunately there seems to be a bug in the GDI+ clipping code that makes the program sometimes draw line segments that cross from one part of the clipping region to another, as shown in the picture on the right. Simply setting the Graphics object's SmoothingMode property to AntiAlias fixes it.
The moral is, if you need to clip features such as line segments to a complex shape, you may need to set the SmoothingMode property to make it work properly. (If you discover another way to fix this, please let me know.)
Download the example to experiment with it and to see additional details.
|