I am looking for or trying to implement an algorithm to draw box shadows (as in the CSS 3 specifiction) which accepts the following parameters:
- Horizontal Offset
- Vertical Offset
- Inset
- Spread
- Blur
- Color
- (Optional: Opacity).
Where to start.
I have looked for Firefox / Chrome source code to see if I can pull an implementation from there, no such luck!
I have looked into linear gradient algorithms, drawing them with a box, which kind of works, except with rounded rectangles it leaves empty pixels in the shadow, presumably due to the radius of the edge.
I am doing this in .NET with GDI+. My aim is NOT to create drop shadows for images. I have already seen articles on this. I want to create drop shadows for shapes drawn with GDI+.
Any help appreciated!
I coded for you a DropShadowPanel that handles controls inside it and adds the shadows (outer or inner) as required by the Tag of the control.
As you can see in the image controls get their shadows as defined:
tags:
textbox: DropShadow:5,5,5,10,#000000,noinset
calendar: DropShadow:10,10,80,30,#0000FF,noinset
picturebox top left: DropShadow:-50,20,50,10,#888888,noinset
picturebox bottom left: DropShadow:10,10,20,20,#442200,inset
picturebox lower right: DropShadow:0,0,50,50,#442200,noinset
Here is the code for the Panel:
(it uses intermediate drawings into an image before drawing to the control gdi object, to not make the form crawl - this actually works pretty fast)
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);
}
}
}
}
Code is written fast without much review so there may be bugs especially if you set wring tags on controls).
PS. You may notice that inner shadow does not work for some controls. This is because they are wrappers around windows system controls. The panel cannot overcome this by itself, but you may do it like here: http://www.codeproject.com/Articles/4548/Generating-missing-Paint-event-for-TreeView-and-Li