Chrome extension icon: 96 px art in a 128 px PNG, from Sume

Chrome's store icon is a 128x128 PNG: 96x96 artwork with 16 px transparent padding per side. Generate a transparent mark on Sume and pad it in Pillow.

5 min readSume
All posts

Chrome's extension icon is a 128 x 128 PNG made of 96 x 96 pixels of artwork plus 16 pixels of transparent padding on every side. The image models on Sume do not know about that padding, so the reliable recipe is to generate a transparent mark with ChatGPT Image 2.5, shrink it to fit 96 x 96, and paste it into a transparent 128 x 128 canvas.

The icon rules on the page

Chrome's image documentation gives the dimensions and two design notes: the image should work well on both light and dark backgrounds, and you should not put an edge around the 128 x 128 image because the UI might add edges. It also asks you to avoid dramatic angles in perspective.

Chrome Web Store extension icon (read 2026-10-03)
PropertyValue
Canvas128 x 128 pixels, PNG
Artwork area96 x 96 pixels
Padding16 pixels of transparent padding on each side
BackgroundsShould work on light and dark
EdgeDo not draw one around the 128 x 128 image

Why transparency matters here

Padding is only useful if it is truly transparent. On Sume, background: "transparent" is documented for ChatGPT Image 2.5 only; the docs add that for transparent stills on other routes you can use the legacy Image 1.0 path with transparency: true. Sending background to a model that does not list it returns 400 unsupported_parameter. Ask for PNG output as well, because JPEG cannot hold an alpha channel.

Generate, trim, pad

The script crops the result to its visible bounding box, so any empty margin the model left is removed, then scales the mark to fit inside 96 x 96 and centers it in the padded canvas. Trimming first means the artwork fills the 96 pixel box instead of floating in it. The request uses a square ratio, and ChatGPT Image 2.5 lists 1:1.

import os, io, requests
from PIL import Image
r = requests.post(
    "https://api.sume.com/v1/images",
    headers={"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"},
    json={"model": "openai/gpt-image-2.5", "aspect_ratio": "1:1",
          "background": "transparent", "output_format": "png",
          "prompt": "single bold blue puzzle piece icon, flat style, no text, no shadow"},
    timeout=90,
)
r.raise_for_status()
if r.status_code != 200:
    raise SystemExit("202: read the job result first")
art = Image.open(io.BytesIO(requests.get(r.json()["data"][0]["url"], timeout=60).content)).convert("RGBA")
art = art.crop(art.getbbox()) if art.getbbox() else art
art.thumbnail((96, 96), Image.LANCZOS)
icon = Image.new("RGBA", (128, 128), (0, 0, 0, 0))
icon.alpha_composite(art, (16 + (96 - art.width) // 2, 16 + (96 - art.height) // 2))
icon.save("icon-128.png", optimize=True)

Why not ask for 128 x 128 directly

The Image API sizes output by aspect ratio, not by exact pixels, and the per-model ratio lists are in the catalog. A request for a 1:1 image gives you a large square that is then reduced locally. Reducing is the better direction: a 1024-pixel mark scaled down to 96 pixels keeps clean edges, while a mark generated small has soft edges that an upscale makes worse. The same logic applies to the toolbar sizes, which you can derive from the one master image with Image.resize.

If you also ship 48 x 48 and 16 x 16 icons for the extensions page and the toolbar, render them from the trimmed master instead of from the padded 128 canvas, so the artwork is not shrunk twice.

What to check

  • Open the file on a white and a near-black background. A dark mark with no outline will vanish on dark.
  • Look at it at 16 x 16 and 32 x 32 too; fine detail turns to noise at small sizes.
  • ChatGPT Image 2.5 is billed from token estimates, so its per-image cost varies with quality; the response usage.cost is the billed amount, and omitted quality defaults to high.
  • Keep the prompt free of words. Icon-sized text is unreadable and models misspell short strings.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume