Search code examples
c#winformsgdi+shadowgraphicspath

A shadow created with PathGradientBrush shows undesired thorn results


I can't understand why the shadow path has thorns near the Arrow Cap.
Refer to my screenshot.

using (GraphicsPath _Path = new GraphicsPath())
{
    Point[] _Points = new Point[] { 
            new Point { X = mouseDownX, Y = mouseDownY }, 
            new Point { X = lineEndX - 51, Y = mouseDownY },
            new Point { X = lineEndX - 51, Y = mouseDownY  - 20 },
            new Point { X = lineEndX, Y = mouseDownY + 5},
            new Point { X = lineEndX -51, Y = mouseDownY + 25},
            new Point { X = lineEndX -51, Y = mouseDownY +10 },
            new Point { X = mouseDownX, Y = mouseDownY +10 }};    
    _Path.AddPolygon(_Points);                                                                                     
    using (PathGradientBrush _Brush = new PathGradientBrush(_Path))
    {
        _Brush.WrapMode = WrapMode.Clamp;
        ColorBlend _ColorBlend = new ColorBlend(3);
        _ColorBlend.Colors = new Color[]{Color.Transparent, 
                                 Color.FromArgb(180, Color.DimGray),
                                 Color.FromArgb(180, Color.DimGray)};
        _ColorBlend.Positions = new float[] { 0f, 0.1f, 1f};
        _Brush.InterpolationColors = _ColorBlend;
        //myGraphics.Clip = new Region(_Path);
        myGraphics.FillPath(_Brush,_Path);
        //myGraphics.ResetClip();
    }
    Matrix _Matrix = new Matrix();
    int _ShadowDistance = -40;
    _Matrix.Translate(_ShadowDistance, _ShadowDistance);
    _Path.Transform(_Matrix);
    myGraphics.FillPath(Brushes.Red, _Path);
}

Screenshot:
PathGradientBrush shadow


Solution

  • One way to correct the problem, is to specify the inner PathGradientBrush's FocusScales, to delimit the color fall-off, without compromising the color blending.

    Unfortunately, the Docs don't actually describe what this property is used for.
    You can read a better description here: How to: Create a Path Gradient

    The fall-off can be adjusted on the color positions. Since you specified:

    Positions = new float[] { 0.0f, 0.1f, 1.0f }
    

    then the color fall-offs can be set to:

    brush.FocusScales = new PointF(0.1f, 1.0f);
    

    The horizontal scale can be adjusted, but within half of the total measure, otherwise the color blending will be compromised: you won't see the transparency anti-aliasing on the edges of the shape.

    A better result is also achieved setting the PixelOffsetMode to PixelOffsetMode.Half.
    Note that the description in the Docs is wrong, refer to the C++ documentation about this setting.

    PathGradientBrush - FocusScales

    A sample implementation. You may want to refresh the drawing only on a MouseDown event.
    mousePosition is the Location of the Mouse pointer. Can be set in the MouseDown event handler.

    private Point mousePosition = Point.Empty;
    private float lineSize = 100.0f;
    private int shadowDistance = 16;
    
    private void someControl_Paint(object sender, PaintEventArgs e)
    {
        e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
        e.Graphics.PixelOffsetMode = PixelOffsetMode.Half;
    
        using (var path = new GraphicsPath(FillMode.Winding)) {
            PointF[] arrowPoints = new PointF[] {
                mousePosition,
                new PointF (mousePosition.X - 20f, mousePosition.Y + 10f),
                new PointF (mousePosition.X - 20f, mousePosition.Y + 3f),
                new PointF (mousePosition.X - lineSize, mousePosition.Y + 3f),
                new PointF (mousePosition.X - lineSize, mousePosition.Y - 3f),
                new PointF (mousePosition.X - 20f, mousePosition.Y - 3f),
                new PointF (mousePosition.X - 20f, mousePosition.Y - 10f)
            };
            path.AddLines(arrowPoints);
    
            using (var brush = new PathGradientBrush(path.PathPoints, WrapMode.Clamp)) {
                var blend = new ColorBlend() {
                    Colors = new Color[] { Color.Transparent,
                                           Color.FromArgb(180, Color.DimGray),
                                           Color.FromArgb(180, Color.DimGray) },
                    Positions = new float[] { 0.0f, 0.2f, 1.0f }
                };
                brush.FocusScales = new PointF(0.2f, 1.0f);
                brush.InterpolationColors = blend;
                e.Graphics.FillPath(brush, path);
            }
            using (var mx = new Matrix()) {
                mx.Translate(-shadowDistance, -shadowDistance);
                e.Graphics.Transform = mx;
                e.Graphics.FillPath(Brushes.Red, path);
            }
        }
    }