4

我正在寻找或尝试实现一种算法来绘制框阴影(如在 CSS 3 规范中),它接受以下参数:

  • 水平偏移
  • 垂直偏移
  • 插图
  • 传播
  • 模糊
  • 颜色
  • (可选:不透明度)。

从哪儿开始。

我一直在寻找 Firefox / Chrome 源代码,看看我是否可以从那里提取一个实现,没有这样的运气!

我研究了线性渐变算法,用一个盒子来绘制它们,这种方法很有效,除了圆角矩形它会在阴影中留下空像素,大概是由于边缘的半径。

我正在使用 GDI+ 在 .NET 中执行此操作。我的目标不是为图像创建阴影。我已经看过这方面的文章。我想为使用 GDI+ 绘制的形状创建阴影。

任何帮助表示赞赏!

4

1 回答 1

14

我为您编写了一个 DropShadowPanel,它处理其中的控件并根据控件标签的要求添加阴影(外部或内部)。

正如您在图像控件中看到的那样,按照定义获得它们的阴影:

标签:

文本框:DropShadow:5,5,5,10,#000000,noinset

日历:DropShadow:10,10,80,30,#0000FF,noinset

图片框左上角:DropShadow:-50,20,50,10,#888888,noinset

图片框左下角:DropShadow:10,10,20,20,#442200,inset

图片右下角:DropShadow:0,0,50,50,#442200,noinset

在此处输入图像描述

这是面板的代码:(它在绘制到控制 gdi 对象之前使用中间绘图到图像中,以不使表单爬行 - 这实际上工作得非常快)

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace WindowsFormsApplication4
{
    public class DropShadowPanel : Panel
    {
        protected override void OnControlAdded(ControlEventArgs e)
        {
            e.Control.Paint += new PaintEventHandler(Control_Paint);
            base.OnControlAdded(e);
        }

    void Control_Paint(object sender, PaintEventArgs e)
    {
        CheckDrawInnerShadow(sender as Control, e.Graphics);
    }

    private void CheckDrawInnerShadow(Control sender, Graphics g)
    {
        var dropShadowStruct = GetDropShadowStruct(sender);

        if (dropShadowStruct == null || !dropShadowStruct.Inset)
        {
            return;
        }

        DrawInsetShadow(sender as Control, g);

    }

    protected override void  OnControlRemoved(ControlEventArgs e)
    {
        e.Control.Paint -= new PaintEventHandler(Control_Paint);
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);
        DrawShadow(Controls.OfType<Control>().Where(c => c.Tag != null && c.Tag.ToString().StartsWith("DropShadow")), e.Graphics);
    }

    void DrawInsetShadow(Control control, Graphics g)
    {
        var dropShadowStruct = GetDropShadowStruct(control);

        var rInner = new Rectangle(Point.Empty, control.Size);

        var img = new Bitmap(rInner.Width, rInner.Height, g);
        var g2 = Graphics.FromImage(img);

        g2.CompositingMode = CompositingMode.SourceCopy;
        g2.FillRectangle(new SolidBrush(dropShadowStruct.Color), 0, 0, control.Width, control.Height);

        rInner.Offset(dropShadowStruct.HShadow, dropShadowStruct.VShadow);
        rInner.Inflate(dropShadowStruct.Blur, dropShadowStruct.Blur);
        rInner.Inflate(-dropShadowStruct.Spread, -dropShadowStruct.Spread);

        double blurSize = dropShadowStruct.Blur;
        double blurStartSize = blurSize;

        do
        {
            var transparency = blurSize/blurStartSize;
            var color = Color.FromArgb(((int)(255 * (transparency * transparency))), dropShadowStruct.Color);               
            rInner.Inflate(-1,-1);
            DrawRoundedRectangle(g2, rInner, (int)blurSize, Pens.Transparent, color);
            blurSize--;
        } while (blurSize > 0);

        g.DrawImage(img, 0, 0);
        g.Flush();

        g2.Dispose();
        img.Dispose();
    }

    void DrawShadow(IEnumerable<Control> controls, Graphics g)
    {
        foreach (var control in controls)
        {
            var dropShadowStruct = GetDropShadowStruct(control);

            if (dropShadowStruct.Inset)
            {
                continue; // must be handled by the control itself
            }

            DrawOutsetShadow(g, dropShadowStruct, control);
        }
    }

    // drawing the loop on an image because of speed
    private void DrawOutsetShadow(Graphics g, dynamic dropShadowStruct, Control control)
    {
        var rOuter = control.Bounds;
        var rInner = control.Bounds;
        rInner.Offset(dropShadowStruct.HShadow, dropShadowStruct.VShadow);
        rInner.Inflate(-dropShadowStruct.Blur, -dropShadowStruct.Blur);
        rOuter.Inflate(dropShadowStruct.Spread, dropShadowStruct.Spread);
        rOuter.Offset(dropShadowStruct.HShadow, dropShadowStruct.VShadow);
        var originalOuter = rOuter;

        var img = new Bitmap(originalOuter.Width, originalOuter.Height, g);
        var g2 = Graphics.FromImage(img);

        var currentBlur = 0;

        do
        {
            var transparency = (rOuter.Height - rInner.Height)/(double) (dropShadowStruct.Blur*2 + dropShadowStruct.Spread*2);
            var color = Color.FromArgb(((int)(255 * (transparency * transparency))), dropShadowStruct.Color);
            var rOutput = rInner;
            rOutput.Offset(-originalOuter.Left, -originalOuter.Top);
            DrawRoundedRectangle(g2, rOutput, currentBlur, Pens.Transparent, color);
            rInner.Inflate(1, 1);
            currentBlur = (int) ((double) dropShadowStruct.Blur*(1 - (transparency*transparency)));
        } while (rOuter.Contains(rInner));

        g2.Flush();
        g2.Dispose();

        g.DrawImage(img, originalOuter);

        img.Dispose();
    }

    private static dynamic GetDropShadowStruct(Control control)
    {
        if (control.Tag == null || !(control.Tag is string) || !control.Tag.ToString().StartsWith("DropShadow"))
            return null;

        string[] dropShadowParams = control.Tag.ToString().Split(':')[1].Split(',');
        var dropShadowStruct = new
                                {
                                    HShadow = Convert.ToInt32(dropShadowParams[0]),
                                    VShadow = Convert.ToInt32(dropShadowParams[1]),
                                    Blur = Convert.ToInt32(dropShadowParams[2]),
                                    Spread = Convert.ToInt32(dropShadowParams[3]),
                                    Color = ColorTranslator.FromHtml(dropShadowParams[4]),
                                    Inset = dropShadowParams[5].ToLowerInvariant() == "inset"
                                };
        return dropShadowStruct;
    }

    private void DrawRoundedRectangle(Graphics gfx, Rectangle bounds, int cornerRadius, Pen drawPen, Color fillColor)
    {
        int strokeOffset = Convert.ToInt32(Math.Ceiling(drawPen.Width));
        bounds = Rectangle.Inflate(bounds, -strokeOffset, -strokeOffset);

        var gfxPath = new GraphicsPath();
        if (cornerRadius > 0)
        {
            gfxPath.AddArc(bounds.X, bounds.Y, cornerRadius, cornerRadius, 180, 90);
            gfxPath.AddArc(bounds.X + bounds.Width - cornerRadius, bounds.Y, cornerRadius, cornerRadius, 270, 90);
            gfxPath.AddArc(bounds.X + bounds.Width - cornerRadius, bounds.Y + bounds.Height - cornerRadius, cornerRadius,
                           cornerRadius, 0, 90);
            gfxPath.AddArc(bounds.X, bounds.Y + bounds.Height - cornerRadius, cornerRadius, cornerRadius, 90, 90);
        }
        else
        {
            gfxPath.AddRectangle(bounds);
        }
        gfxPath.CloseAllFigures();

        gfx.FillPath(new SolidBrush(fillColor), gfxPath);
        if (drawPen != Pens.Transparent)
        {
            var pen = new Pen(drawPen.Color);
            pen.EndCap = pen.StartCap = LineCap.Round;
            gfx.DrawPath(pen, gfxPath);
        }
    }
    }
}

代码编写很快,没有太多审查,因此可能存在错误,特别是如果您在控件上设置了 wring 标签)。

PS。您可能会注意到内部阴影不适用于某些控件。这是因为它们是 Windows 系统控件的包装器。小组无法自行克服这一点,但您可以在这里进行:http: //www.codeproject.com/Articles/4548/Generating-missing-Paint-event-for-TreeView-and-Li

于 2012-11-30T21:05:29.407 回答