On this page

Adding Drop Shadow under a PNG image

One simple way to enhance the aesthetic appeal of a photo is to add a drop shadow. A drop shadow is a visual effect that makes an object appear to be floating above the background, giving it a sense of depth and dimensionality. This can make an image look more polished and professional, and it can also draw the viewer’s eye to the subject of the photo. The following example is an application in OpenCV. You can replicate the same steps in other frameworks or programming languages. We will use the following image as foreground and background, respectively (images are generated with Stable Diffusion model):

Sample Foreground

Sample Background

We will apply the following steps:

  1. Extract the alpha channel from the foreground. It is the transparency channel.
  2. Add blur to alpha
  3. Put the blurred alpha channel onto the background
  4. Put the PNG image on top of the composition

Adding Drop Shadow

OpenCV Python

import cv2
import numpy as np

FG_IMG_PATH = "fg.png"
BG_IMG_PATH = "bg.jpeg"
BLUR_AMOUNT = 32

def load_image(path, color_conversion=None):
    """Load an image and optionally convert its color."""
    image = cv2.imread(path, cv2.IMREAD_UNCHANGED)
    if color_conversion:
        image = cv2.cvtColor(image, color_conversion)
    return image

def extract_alpha_channel(image):
    """Extract the alpha channel and the RGB channels from an image."""
    alpha_channel = image[:,:,3]
    rgb_channels = image[:,:,0:3]
    return alpha_channel, rgb_channels

def apply_blur_to_alpha(alpha, blur_amount):
    """Apply blur to the alpha channel."""
    return cv2.blur(alpha, (blur_amount, blur_amount))

def expand_and_normalize_alpha(alpha):
    """Expand alpha dimensions and normalize its values to the range [0,1]."""
    expanded_alpha = np.expand_dims(alpha, axis=2)
    repeated_alpha = np.repeat(expanded_alpha, 3, axis=2)
    normalized_alpha = repeated_alpha / 255
    return normalized_alpha

def create_shadow_on_bg(bg, alpha_blur):
    """Put shadow (based on blurred alpha) on top of the background."""
    black_canvas = np.zeros(bg.shape, dtype=np.uint8)
    shadowed_bg = (alpha_blur * black_canvas + (1 - alpha_blur) * bg).astype(np.uint8)
    return shadowed_bg

def composite_foreground_on_bg(fg, alpha, bg_with_shadow):
    """Put the foreground image on top of the background with shadow."""
    composited_image = (alpha * fg + (1 - alpha) * bg_with_shadow).astype(np.uint8)
    return composited_image

if __name__ == "__main__":
    # Load images and convert their color if necessary
    fg = load_image(FG_IMG_PATH, cv2.COLOR_BGRA2RGBA)
    bg = load_image(BG_IMG_PATH, cv2.COLOR_RGB2BGR)

    # Extract alpha and RGB channels from the foreground image
    alpha, fg_rgb = extract_alpha_channel(fg)

    # Blur the alpha channel to get the shadow
    alpha_blur = apply_blur_to_alpha(alpha, BLUR_AMOUNT)

    # Expand and normalize the blurred alpha for shadow calculation
    alpha_blur_normalized = expand_and_normalize_alpha(alpha_blur)

    # Create a version of the background with the shadow
    bg_with_shadow = create_shadow_on_bg(bg, alpha_blur_normalized)

    # Expand and normalize the original alpha for compositing foreground over background
    alpha_normalized = expand_and_normalize_alpha(alpha)

    # Composite the foreground on the shadowed background
    final_image = composite_foreground_on_bg(fg_rgb, alpha_normalized, bg_with_shadow)

    # Display the final image (optional)
    cv2.imshow("Final Image", final_image)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

Image with Shadow

Pillow

If you want to apply the same effect with Pillow (which is a high-level imaging library in Python):

from PIL import ImageOps, Image, ImageFilter

FG_IMG_PATH = "fg.png"
BG_IMG_PATH = "bg.jpeg"

def load_image(path):
    """Load an image using PIL."""
    return Image.open(path)

def extract_alpha(image):
    """Extract the alpha channel from an image."""
    return image.split()[-1]

def create_shadow_from_alpha(alpha, blur_radius):
    """Create a shadow based on a blurred version of the alpha channel."""
    alpha_blur = alpha.filter(ImageFilter.BoxBlur(blur_radius))
    shadow = Image.new(mode="RGB", size=alpha_blur.size)
    shadow.putalpha(alpha_blur)
    return shadow

def composite_images(bg, fg, shadow):
    """Composite the shadow and foreground onto the background."""
    bg.paste(shadow, (0, 0), shadow)
    bg.paste(fg, (0, 0), fg)
    return bg

if __name__ == "__main__":
    # Load the images
    fg = load_image(FG_IMG_PATH)
    bg = load_image(BG_IMG_PATH)

    # Create the shadow based on the alpha channel of the foreground
    alpha = extract_alpha(fg)
    shadow = create_shadow_from_alpha(alpha, blur_radius=25)

    # Composite the shadow and foreground onto the background
    final_image = composite_images(bg, fg, shadow)

    # Display the final image (optional)
    final_image.show()

HTML Canvas

Here is the same effect in HTML & JavaScript:

HTML:

<!DOCTYPE html>
<html>
<body>
<canvas id="dropShadowCanvas" width="800" height="600" style="border:1px solid #000000;">
Your browser does not support the HTML canvas tag.</canvas>
</body>
</html>

JavaScript:

// Get the canvas element and its context
var canvas = document.getElementById("dropShadowCanvas");
var ctx = canvas.getContext("2d");

// Load the background image
var bgImage = new Image();
bgImage.onload = function() {
    // Set canvas size to background size
    canvas.width = bgImage.width;
    canvas.height = bgImage.height;

    // Draw the background
    ctx.drawImage(bgImage, 0, 0);

    // Load and draw the PNG image with shadow
    var pngImage = new Image();
    pngImage.onload = function() {
        // Calculate center position
        var x = (canvas.width - pngImage.width) / 2;
        var y = (canvas.height - pngImage.height) / 2;

        // Set shadow properties
        ctx.shadowColor = 'rgba(0, 0, 0, 1)';
        ctx.shadowBlur = 30;
        ctx.shadowOffsetX = 0;
        ctx.shadowOffsetY = 0;

        // Draw the PNG image
        ctx.drawImage(pngImage, x, y);
    };
    pngImage.src = 'https://media.withoutbg.com/blog/adding-shadow-under-image/fg.png';
};
bgImage.src = 'https://media.withoutbg.com/blog/adding-shadow-under-image/bg.jpeg';

Edit on Codepen