CSS3-like implementation / shadow window algorithm

I am looking for or trying to implement an algorithm for drawing box shadows (as in the CSS 3 specification) that takes the following parameters:

  • Horizontal displacement
  • Vertical displacement
  • Sidebar
  • Spread
  • Blur
  • Color
  • (Optional: Opacity).

Where to begin.

I searched the source code for Firefox / Chrome to find out if I can implement the implementation there, there is no such luck!

I looked at the linear gradient algorithms, drawing them using the window, which works, except for 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 goal is not to create shadows for images. I have already seen articles about this. I want to create shadows for shapes drawn using GDI +.

Any help appreciated!

+4
source share
1 answer

I coded for you a DropShadowPanel that processes the controls inside it and adds shadows (external or internal) as required by the control tag.

As you can see in the image controls, their shadows are defined as:

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

bottom left picture: DropShadow: 10,10,20,20, # 442200, insert

right shot: DropShadow: 0,0,50,50, # 442200, noinset

enter image description here

Here is the code for the panel: (it uses intermediate drawings in the image before drawing on the gdi control object, so as not to perform form traversal - it really works pretty quickly)

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); } } } } 

The code is written quickly without much review, so there may be errors, especially if you set labels on the controls).

PS. You may notice that the inner shadow does not work for some controls. This is because they are wrappers around Windows system controls. The panel cannot overcome this on its own, but you can do it like this: http://www.codeproject.com/Articles/4548/Generating-missing-Paint-event-for-TreeView-and-Li

+11
source

Source: https://habr.com/ru/post/1440161/


All Articles