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.

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.
| Look | Blur radius | Offset | Opacity |
|---|---|---|---|
| Subtle contact | 8 | (0, 8) | 0.45 |
| Soft studio | 20 | (0, 24) | 0.35 |
| Heavy floating | 40 | (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
- soundtrack_fade_exceeds_output: a bed fade longer than a short clip
Sume rejects soundtrack.fade_out_seconds longer than audio.duration_seconds. The fade also caps at 10 s. For a 6 s Short, use a 1 to 2 s fade.
- Timeline output size: 256 to 2160 per side, so no 3840x2160
Sume Timeline 1.0 takes even output width and height from 256 to 2160, default 1080x1920. What that means for 4K, 2560x1080 ultrawide and portrait masters.
- Swap the offer tail on 40 winning video ads for Cyber Monday: $12.07
Keep the first 12 s of 40 Black Friday winners, cut to a new end card in Timeline at $0.10 each, and burn the Cyber Monday code with $0.20 cues: $12.07 in all.
- Tall phone screen recording to 9:16: crop height 0.8215 and a y offset
A 1170x2532 screen recording is taller than 9:16. Keep the full width, crop height 0.8215, and choose y to drop the status bar. Request and numbers.
Written by Sume