Put a 4:5 AI image on a 9:16 canvas with blurred fill in Pillow

Fill the empty bands of a 9:16 canvas with a blurred, darkened copy of the same Sume image, and place the sharp 4:5 original over it. Code and blur settings.

5 min readSume
All posts

Build a 1080x1920 canvas by scaling a copy of the image with ImageOps.fit, blurring it with GaussianBlur(40), dimming it, then pasting the sharp 1080x1350 original in the middle. A 4:5 image on a 9:16 canvas leaves 570 pixels of empty height in total, which is 285 above and 285 below, and the blurred copy fills that without black bars.

The arithmetic

1080 wide at 4:5 is 1350 tall. A 9:16 canvas at the same width is 1920 tall. The difference is 570 pixels, split evenly. A 1:1 image fills 1080, leaving 840, or 420 each side.

Sume's Nano Banana models list both 4:5 and 9:16, so you can generate at 4:5 and pad, or generate 9:16 directly and skip this. Padding is useful when you already have a 4:5 feed asset and want the story version from the same file without a second paid call.

The code

The background is the same image scaled to cover the whole canvas, which keeps its colors, and the blur removes detail so it does not compete with the foreground. Darkening by about 40 percent keeps text readable on top.

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 ImageEnhance, ImageFilter, ImageOps
out = gen(model="google/nano-banana-2", aspect_ratio="4:5",
          prompt="Overhead flat lay of a green smoothie, strawberries and a glass straw")
img = fetch(out["data"][0]["url"]).convert("RGB")
fg = ImageOps.fit(img, (1080, 1350), Image.LANCZOS)
bg = ImageOps.fit(img, (1080, 1920), Image.LANCZOS)
bg = bg.filter(ImageFilter.GaussianBlur(40))
bg = ImageEnhance.Brightness(bg).enhance(0.6)
bg.paste(fg, (0, (1920 - 1350) // 2))
bg.save("story_blurfill.jpg", quality=92)
print(bg.size, "pad each side", (1920 - 1350) // 2, "cost", out["usage"]["cost"])

Padding by source ratio

Pad height at 1080 width for common inputs. The blur matters most when the bands are tall.

Empty height on a 1080x1920 canvas, arithmetic read 2026-10-05
Source ratioImage height at 1080 wideEmpty totalEach side
4:51350570285
1:11080840420
16:9608 (rounded)1312656

Tuning and limits

  • Bigger blur radius makes a smoother backdrop. Under 20 shows ghost shapes.
  • Brightness 0.6 works for light photos. Dark images can stay at 1.0.
  • Keep captions and logos inside the platform's safe area, not in the bands at the top and bottom edges.
  • For a 16:9 source the bands are very tall. Consider a cover crop instead.

Related

To make a 9:16 image by resizing, see the story image recipe. Export other widths from the same master with the srcset recipe, and keep lossless intermediates per PNG between passes.

Sources

Related posts

More in Media tools

All Media tools posts

Written by Sume