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):
We will apply the following steps:
- Extract the alpha channel from the foreground. It is the transparency channel.
- Add blur to alpha
- Put the blurred alpha channel onto the background
- Put the PNG image on top of the composition
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()
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';