Adding Drop Shadow under a PNG image

Author: Imran Kocabiyik

Image without background

Adding Shadow Under an Object

Thanks to deep learning, image background removal can now be automated. There might be use cases in which the image without a background is not used alone, but it is placed on top of another image. When there is a difference in lighting conditions between the foreground layer (image without background) and the background layer, the result may look artificial. To overcome this issue, there might be needed post-processing steps. In this post, I will show how to add shadows under the image to make the final image more realistic.

We will focus on processing images programmatically. 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):
Foreground Image
Background Image

We will apply the following steps:

  1. Extract the alpha channel from the foreground. It is the forth channel in the PNG images.
  2. Apply blur on the alpha channel. Let's call it $\alpha_b$.
  3. Expand the dimension of the blurred alpha channel, make sure it has the same format as RGB images.
  4. Create a temporary blank canvas. Let's call it $BL$.
  5. Put shadow on the background ($BG$) by applying image compositing: $\alpha_b BL + (1-\alpha_b)BG$. This will generate a background with shadows. Let's call it $BS$.
  6. Put foreground ($FG$) on top of background with shadow ($BS$): $ \alpha_b FG + (1-\alpha_b)BS $.

OpenCV Python

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

fg = cv2.imread(FG_IMG_PATH, cv2.IMREAD_UNCHANGED)
fg = cv2.cvtColor(fg, cv2.COLOR_BGRA2RGBA)

bg = cv2.imread(BG_IMG_PATH)
bg = cv2.cvtColor(bg, cv2.COLOR_RGB2BGR)

# step 1: extract alpha channel
alpha = fg[:,:,3]
fg = fg[:,:,0:3]

# step 2: apply blur
alpha_blur = cv2.blur(alpha, (BLUR_AMOUNT, BLUR_AMOUNT))

# step 3: expand the dimension of the blurred alpha channel
alpha_blur = np.expand_dims(alpha_blur, axis=2)
alpha_blur = np.repeat(alpha_blur, 3, axis=2)
alpha_blur = alpha_blur/255

# step 4: create a temporary blank canvas
black = np.zeros(bg.shape, dtype=np.uint8) # blank canvas

# step 5: put shadow on top of the background
bg_with_shadow = (alpha_blur*black+(1-alpha_blur)*bg).astype(np.uint8)

# step 6: put foreground on top of the background with shadow on it
alpha = np.expand_dims(alpha, axis=2)
alpha = np.repeat(alpha, 3, axis=2)
alpha = alpha/255

image = (alpha*fg+(1-alpha)*bg_with_shadow).astype(np.uint8)
image

Image with Shadow Under

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"

fg = Image.open(FG_IMG_PATH)
bg = Image.open(BG_IMG_PATH)

alpha = fg.split()[-1]
alpha_blur = alpha.filter(ImageFilter.BoxBlur(25))

shadow = Image.new(mode="RGB", size=alpha_blur.size)
shadow.putalpha(alpha_blur)

bg.paste(shadow, (0, 0), shadow)
bg.paste(fg, (0,0), fg)

bg

Summary

To add drop shadow, we simply generated a blurred image using the foregound image and pasted it to the background layer. After that, we put the foreground image on top of them.