Search code examples
c#wpfeffectdrawingvisualdropshadoweffect

How to calculate the size of a DropShadowEffect?


I'm trying to get an image and apply a drop shadow to it and save back as an image.

So far, the only way to do that, while not using third party solutions is to use the DropShadowEffect in a DrawingVisual:

var drawingVisual = new DrawingVisual();
drawingVisual.Effect = new DropShadowEffect
{
    Color = Color.FromArgb(255, 0, 0, 0),
    BlurRadius = 5,
    Opacity = 1,
    Direction = 45,
    ShadowDepth = 6
};

using (var drawingContext = drawingVisual.RenderOpen())
{
    var left = 0; //??
    var top = 0; //??
    var totalWidth = left + image.Width; //??
    var totalHeight = top + image.Height; //??

    //Background.
    drawingContext.DrawRectangle(new SolidColorBrush(Colors.White), null, new Rect(0,0, totalWidth, totalHeight));

    //Image.
    drawingContext.DrawImage(image, new Rect(left, top, image.Width, image.Height));
}

var frameHeight = image.PixelHeight; //??
var frameWidth = image.PixelWidth; //??

//Converts the Visual (DrawingVisual) into a BitmapSource.
var bmp = new RenderTargetBitmap(frameWidth, frameHeight, imageDpi, imageDpi, PixelFormats.Pbgra32);
bmp.Render(drawingVisual);

//Creates a PngBitmapEncoder and adds the BitmapSource to the frames of the encoder.
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bmp));

//Saves the image into a file using the encoder.
using (Stream stream = File.Create(frame.Path))
    encoder.Save(stream);

I have no idea the math required to detect the exact pixel offsets at all sides for a given DropShadowEffect.

Is there any built in way to measure it or should I do it manually? How can be done manually?


Solution

  • If you take a look at the .NET source code for the DropShadowEffect there is an internal method GetRenderBounds that you can use to formulate the answer you're looking for.

    While GetRenderBounds is not available for you to use, the code is simple enough that you can create your own helper method.

    Here's the code for reference:

    /// <summary>
    /// Takes in content bounds, and returns the bounds of the rendered
    /// output of that content after the Effect is applied.
    /// </summary>
    internal override Rect GetRenderBounds(Rect contentBounds)
    {
        Point topLeft = new Point();
        Point bottomRight = new Point();
    
        double radius = BlurRadius;
        topLeft.X = contentBounds.TopLeft.X - radius;
        topLeft.Y = contentBounds.TopLeft.Y - radius;
        bottomRight.X = contentBounds.BottomRight.X + radius;
        bottomRight.Y = contentBounds.BottomRight.Y + radius;
    
        double depth = ShadowDepth;
        double direction = Math.PI/180 * Direction;
        double offsetX = depth * Math.Cos(direction);
        double offsetY = depth * Math.Sin(direction);
    
        // If the shadow is horizontally aligned or to the right of the original element...
        if (offsetX >= 0.0f)
        {
            bottomRight.X += offsetX;
        }
        // If the shadow is to the left of the original element...
        else
        {
            topLeft.X += offsetX;
        }
    
        // If the shadow is above the original element...
        if (offsetY >= 0.0f)
        {
            topLeft.Y -= offsetY;
        }
        // If the shadow is below the original element...
        else 
        {
            bottomRight.Y -= offsetY;
        }
    
        return new Rect(topLeft, bottomRight);
    }
    

    I hope this helps.

    Update from the author of the question

    Here's the actual code that I'm using:

    //Draws image with shadow.
    using (var drawingContext = drawingVisual.RenderOpen())
    {
        //Measure drop shadow space.
        var point1 = new Point(0 - model.BlurRadius / 2d, 0 - model.BlurRadius / 2d);
        var point2 = new Point(image.PixelWidth + model.BlurRadius / 2d, image.PixelHeight + model.BlurRadius / 2d);
    
       var num1 = Math.PI / 180.0 * model.Direction;
       var num2 = model.Depth * Math.Cos(num1);
       var num3 = model.Depth * Math.Sin(num1);
    
       if (num2 >= 0.0)
           point2.X += num2; //If the shadow is horizontally aligned or to the right of the original element...
       else
           point1.X += num2; //If the shadow is to the left of the original element...
    
       if (num3 >= 0.0)
           point1.Y -= num3; //If the shadow is above the original element...
       else
           point2.Y -= num3; //If the shadow is below the original element...
    
       var left = Math.Abs(point1.X);
       var top = Math.Abs(point1.Y);
       var totalWidth = left + point2.X;
       var totalHeight = top + point2.Y;
    
       //Image.
       drawingContext.DrawImage(image, new Rect((int)left, (int)top, image.PixelWidth, image.PixelHeight));
    
       frameHeight = (int)totalHeight;
       frameWidth = (int)totalWidth;
    }