类似CSS3的盒子阴影实现/算法

时间:2012-10-16 22:16:12

标签: c# .net gdi+ dropshadow

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

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

从哪里开始。

我已经查找了Firefox / Chrome源代码,看看我是否可以从那里获取实现,没有这样的运气!

我已经研究了线性渐变算法,用一个盒子绘制它们,哪种工作,除了圆角矩形,它在阴影中留下空像素,可能是由于边缘的半径。

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

任何帮助表示赞赏!

1 个答案:

答案 0 :(得分:13)

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

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

标记:

textbox: 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,插页

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

enter image description here

以下是Panel的代码: (它在绘制到控件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);
        }
    }
    }
}

快速编写代码而不需要太多评论,因此如果您在控件上设置了扭曲标记,可能会出现错误。)

PS。您可能会注意到内部阴影对某些控件不起作用。这是因为它们是Windows系统控件的包装器。小组不能单独解决这个问题,但您可以像这里一样:http://www.codeproject.com/Articles/4548/Generating-missing-Paint-event-for-TreeView-and-Li