Soft drop shadow under an AI product cutout with Pillow

Give a transparent product PNG from Sume a soft drop shadow: copy the alpha, blur it, offset it, tint it, then composite. Parameters for subtle and heavy looks.

5 min readSume
All posts

Take the cutout's alpha channel, blur it with GaussianBlur, offset it down and to one side, fill it with a dark color at partial opacity, and put the product on top. For a 1024 pixel product, a blur radius of 20, an offset of (0, 24), and 35 percent opacity give a soft studio shadow, with no extra model call.

Where the cutout comes from

ChatGPT Image 2.5 on Sume accepts background: "transparent", which returns a product with no backdrop. Other models do not accept that parameter and return 400 unsupported_parameter, so for those you remove the background in a separate step. Either way, you end up with an RGBA image whose alpha channel is the product's silhouette.

A silhouette is exactly what a shadow needs: the shape of the object, in one channel.

Build the shadow

The shadow layer is solid dark color whose alpha is the blurred silhouette times an opacity. Pad the canvas first so the blur is not clipped at the edges.

import os, io, requests
from PIL import Image
H = {"Authorization": "Bearer " + os.environ["SUME_API_KEY"]}
def gen(**body):
    r = requests.post("https://api.sume.com/v1/images", json=body, headers=H, timeout=60)
    r.raise_for_status()
    if r.status_code == 202:
        raise SystemExit("queued, read /v1/jobs/{id}/result: " + r.text)
    return r.json()
def fetch(u):
    return Image.open(io.BytesIO(requests.get(u, timeout=60).content))
from PIL import ImageFilter
out = gen(model="openai/gpt-image-2.5", background="transparent", output_format="png",
          prompt="Single white sneaker, three-quarter view, studio product photo")
item = fetch(out["data"][0]["url"]).convert("RGBA")
m = 80
canvas = Image.new("RGBA", (item.width + 2 * m, item.height + 2 * m), (255, 255, 255, 255))
a = Image.new("L", canvas.size, 0)
a.paste(item.getchannel("A"), (m, m))
a = a.filter(ImageFilter.GaussianBlur(20))
a = a.point(lambda v: int(v * 0.35))
shadow = Image.new("RGBA", canvas.size, (20, 20, 30, 0))
shadow.putalpha(a)
canvas.alpha_composite(shadow, (0, 24))
canvas.alpha_composite(item, (m, m))
canvas.convert("RGB").save("sneaker_shadow.jpg", quality=92)
print("cost", out["usage"]["cost"])

Shadow presets

Light direction decides offset, and softness decides blur. Keep the same preset across a catalog so shelves look consistent.

Shadow presets for a 1024 px product, values read 2026-10-05
LookBlur radiusOffsetOpacity
Subtle contact8(0, 8)0.45
Soft studio20(0, 24)0.35
Heavy floating40(30, 60)0.25

Common mistakes

  • Forgetting to pad the canvas. The blurred shadow gets cut off at the border.
  • A pure black shadow looks pasted on. Use a dark tint close to the backdrop hue.
  • Blurring the RGB instead of the alpha leaves color fringes. Blur only the alpha.
  • A shadow that points a different way in each shot of a set breaks the look. Fix the offset in one place.

Finish and deliver

Keep the PNG with alpha as the master, and export the white-backdrop JPEG as in the code only for listings. Derive widths from one master using the srcset recipe, and avoid repeated JPEG saves while you iterate, as in PNG between passes. The transparent PNG note covers when to trust the model's alpha.

Sources

Related posts

More in Media tools

All Media tools posts

Written by Sume