AI enamel pin design via API: flat art and a die-line in Pillow
Generate flat enamel-pin art on a transparent background, count the fills, and draw a cut line around the alpha shape in Pillow. Code and checks included.

An enamel pin design needs three things from an image model and one thing from you: flat colour fills, dark separating lines, a clean silhouette on a transparent background, and then a die-line that traces the shape. Sume's openai/gpt-image-2.5 supports background: "transparent", so the first three come from the model, and the cut line is a few lines of Pillow.
This recipe produces a draft for a manufacturer to quote and refine; it does not replace their artwork requirements, which differ by supplier. Ask for their spec first, and use the parameters below to match it.
Prompt for fills and lines
Hard enamel and soft enamel both read as colour areas divided by raised metal lines. A prompt that gets close: flat design, bold uniform outlines, solid fills, a limited palette of six colours, no gradients, no shadows, no text, one clear silhouette, centred. Make one request with n: 4 to see variations; GPT Image 2.5 accepts n from 1 to 4 in the catalog, and usage.cost on the response shows what the call billed.
Check the transparent corners before going further. If the background came back opaque, the alpha shape will be a rectangle and the die-line will be useless.
import os, requests
r = requests.post("https://api.sume.com/v1/images", timeout=120, json={
"model": "openai/gpt-image-2.5",
"prompt": ("Flat enamel pin design of a lighthouse, bold uniform outlines, solid fills, "
"six colors, no gradients, no shadows, no text, one clear silhouette, centered"),
"aspect_ratio": "1:1", "background": "transparent",
"output_format": "png", "n": 4, "quality": "medium",
}, headers={"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"})
r.raise_for_status()
if r.status_code == 202:
raise SystemExit("queued: poll the job status_url")
for i, item in enumerate(r.json()["data"]):
print(i, item["url"])
print("billed:", r.json()["usage"]["cost"])Draw the die-line from the alpha
The cut line follows the silhouette, a little outside it. Pillow can grow the alpha channel with a max filter, subtract the original alpha, and leave a ring you can paint in a spot colour. The function below returns a copy with that ring added and prints the number of distinct visible colours so you can compare it with the supplier's limit.
The offset in pixels depends on the resolution you generate at, so make it a parameter, not a constant.
| Check | How | Fail means |
|---|---|---|
| Transparent corners | alpha at (0, 0) is 0 | background came back opaque; regenerate |
| Fill count | count distinct visible colours | too many for the supplier; reduce or regenerate |
| Silhouette | one connected alpha shape | floating islands may not cast as one piece |
| Hairlines | visually inspect outlines at print size | lines too thin to cast |
from PIL import Image, ImageChops, ImageFilter
def add_dieline(path, out, grow_px=9, line=(255, 0, 255, 255)):
img = Image.open(path).convert("RGBA")
alpha = img.getchannel("A").point(lambda a: 255 if a > 127 else 0)
raw = img.tobytes()
colors = {raw[i:i + 3] for i in range(0, len(raw), 4) if raw[i + 3] > 127}
grown = alpha.filter(ImageFilter.MaxFilter(grow_px * 2 + 1))
ring = ImageChops.subtract(grown, alpha)
ring_img = Image.new("RGBA", img.size, line)
ring_img.putalpha(ring)
Image.alpha_composite(img, ring_img).save(out)
return len(colors), alpha.getpixel((0, 0)) == 0
if __name__ == "__main__":
demo = Image.new("RGBA", (120, 120), (0, 0, 0, 0))
for x in range(30, 90):
for y in range(30, 90):
demo.putpixel((x, y), (200, 40 + x, 60, 255))
demo.save("pin.png")
print(add_dieline("pin.png", "pin-dieline.png", grow_px=6))From draft to quote
Send the supplier the clean art and the die-line version as two files, and say which colours you intend as fills. Keep the original generation URL and prompt next to the file so you can regenerate a variant that matches the supplier's feedback.
For related cutout recipes see the sticker generator and the embroidery patch version of this recipe.
Sources
Related posts
More in Use cases
- AI illustrated map poster via API: art first, labels in code
Generate the illustrated map art at 2:3 with Nano Banana 2, leave the lettering out, then draw every place name in Pillow from a list so none are misspelled.
- AI memory match game cards: 8 pairs, one style, deck built in code
Make a memory-match deck with the image API: eight subjects in one style via a reference image, then duplicate, shuffle and lay out the deck in Pillow.
- AI postcard via API: 3:2 front art, address side drawn in code
Make a postcard front at 3:2 with GPT Image 2.5 custom pixels, then draw the back with message, address lines and a stamp box in Pillow. Code runs as written.
- AI shopping referrals up 693%: a Sume output schema for listing copy
Adobe data in KTVZ has AI referrals to US retail up 693.4% in Nov-Dec 2025. Bind a headline, hero image and alt-text schema so each video returns typed JSON.
Written by Sume