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.
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.
| Property | Value |
|---|---|
| Canvas | 128 x 128 pixels, PNG |
| Artwork area | 96 x 96 pixels |
| Padding | 16 pixels of transparent padding on each side |
| Backgrounds | Should work on light and dark |
| Edge | Do 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.costis 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
- Chrome extension icons 16/32/48/128 from one Sume master
Chrome's manifest icons are PNG-preferred at 16, 32, 48 and 128 px, and SVG is not supported. Make one 1:1 master on Sume and downsample it in Pillow.
- Chrome Web Store marquee 1400x560: crop a 21:9 Sume image
The optional marquee is 1400x560, a 5:2 ratio no Sume model lists. Generate 21:9 on FLUX.2 Pro and crop 6.7% of the height, or lose 20% from a 2:1 image.
- Claude Agent SDK 0.2.160: background wait ceiling, Sume video job
Claude Agent SDK Python 0.2.160 keeps stdin open until background subagents go idle, ten minutes by default. Why a Sume video job can outlast that.
- Claude Code 2.1.288 hooks fail closed: a Sume spend guard script
Claude Code 2.1.288 now blocks a tool call when a PreToolUse hook can't match or serialize input. A small guard script for Sume paid calls.
Written by Sume