Title: Use fuzzy lines to draw shadows in C#
One way to draw shadows in C# is to draw an object shifted down and to the right in a light gray color as shown on the left in the picture. This works fairly well but isn't completely convincing because the edges of the shadow are too crisp. Real shadows blur near the edges.
The image on the right uses a different approach to draw shadows and achieves a slightly better result. To draw shadows, it first draws a thick semi-transparent line. It then draws a series of lines that are successively thinner and more opaque. That makes the shadow line faint on the edges and darker in the middle.
The following code shows how the example draws the image on the right.
// Draw a smiley with a fuzzy line shadow.
private void picFuzzy_Paint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
DrawBackground(e.Graphics, picFuzzy.ClientSize);
// Make the smiley path.
using (GraphicsPath path = MakeSmileyPath(picThin.ClientSize))
{
// Draw the shadow.
e.Graphics.TranslateTransform(6, 6);
DrawPathWithFuzzyLine(path, e.Graphics, Color.Black,
200, 20, 2);
e.Graphics.ResetTransform();
// Draw the face.
using (Pen thick_pen = new Pen(Color.Black, 3))
{
e.Graphics.DrawPath(thick_pen, path);
}
}
}
The code calls the MakeSmileyPath method to make a GraphicsPath containing the ellipses and the arc that make up the smiley face. That method is straightforward so it isn't shown here. Down the example to see how it works.
The code applies a translation to the Graphics object to move the results 6 pixels to the right and down. It then calls the DrawPathWithFuzzyLine method to draw shadows with a fuzzy pen. The code resets the Graphics object's transformation and draws the path again in black.
The following code shows the DrawPathWithFuzzyLine method.
// Use a series of pens with decreasing widths and
// increasing opacity to draw a GraphicsPath.
private void DrawPathWithFuzzyLine(GraphicsPath path,
Graphics gr, Color base_color, int max_opacity,
int width, int opaque_width)
{
// Number of pens we will use.
int num_steps = width - opaque_width + 1;
// Change in alpha between pens.
float delta = (float)max_opacity / num_steps / num_steps;
// Initial alpha.
float alpha = delta;
for (int thickness = width; thickness >= opaque_width;
thickness--)
{
Color color = Color.FromArgb(
(int)alpha,
base_color.R,
base_color.G,
base_color.B);
using (Pen pen = new Pen(color, thickness))
{
pen.EndCap = LineCap.Round;
pen.StartCap = LineCap.Round;
gr.DrawPath(pen, path);
}
alpha += delta;
}
}
The width parameter gives the total width of the fuzzy line. The opaque_width parameter gives the width of the line's center where it has maximum opacity. For example, if width = 20 and opaque_width = 10, the method would draw a line that was a total of 20 pixels wide with a 10 pixel wide strip in the middle at high opacity and then dropping off in opacity to the edges.
This code calculates the number of lines it should draw and the amount by which the opacity must change between each line. It then loops through the line thickness values that it must draw. For each line, it creates a pen with the appropriate opacity, draws the path with that pen, and increases the opacity by the appropriate amount.
Note that the center of the line is redrawn every time a new line is drawn. That means the inner parts of the line get darker each time they are drawn.
This method works well for drawing the shadows of lines, but it doesn't work as well for making shadows for filled areas. If you fill an area and then try to outline it with a fuzzy line, the places where the fuzzy lines overlap the filled area get darker making the area look lighter in the middle.
Download the example to experiment with it and to see additional details.
|