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.

5 min readSume
All posts

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.

Checks to run on each generated pin draft (read 2026-10-04)
CheckHowFail means
Transparent cornersalpha at (0, 0) is 0background came back opaque; regenerate
Fill countcount distinct visible colourstoo many for the supplier; reduce or regenerate
Silhouetteone connected alpha shapefloating islands may not cast as one piece
Hairlinesvisually inspect outlines at print sizelines 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

All Use cases posts

Written by Sume