Image models can draw a die-cut sticker with a white border, but they do not reliably hand you a clean transparent PNG. The sticker maker on PiCraft (picraft.ai) asks the model for the art on a flat grey plate, removes the plate with a hosted background-removal API, and exports two fixed-size PNGs. This post follows that path through the backend code: what the prompt asks for, how the files are produced, and one detail of the resize step that is easy to miss.
The files below are from the backend repository at commit f9cbea5 (2026-10-19): app/services/deliverable.py, app/scenes/loader.py and scenes/sticker-maker.json. The working tree had uncommitted changes when I read it on 2026-11-04, but none of them touch the sticker path: deliverable.py only gained a new function at the end, loader.py gained one dataclass field, and tasks.py gained a branch for a different kind of generator. I did not call the model or the matting API for this post; the one experiment below runs a function extracted from the backend on synthetic images.
What the model is asked for
The prompt for the sticker scene never mentions alpha or transparency. It asks for a die-cut design on a uniform plate:
... Centered, filling about 80% of the image, on a perfectly flat, uniform medium-light grey background (#C8C8C8) with no shadow, no texture, no gradient ... That is the design decision the module's docstring spells out: AI models do not reliably produce clean transparent PNGs, so the pipeline is "solid background → matting → transparent PNG" instead of trusting alpha from the generator.
The scene asks the model for a 1:1 image at its 1K setting and declares two outputs, both with "transparent": true:
| Output id | Label in the scene | Pixels | DPI |
|---|---|---|---|
sticker | Transparent PNG 2048×2048 | 2048 × 2048 | 300 |
print-3in | Print PNG 3×3 in, 300 DPI | 900 × 900 | 300 |
The print file has no width or height in the JSON. resolve_output in loader.py turns inches into pixels:
if "widthIn" in spec: bleed = spec.get("bleedIn", 0) out["width"] = round((spec["widthIn"] + 2 * bleed) * dpi) out["height"] = round((spec["heightIn"] + 2 * bleed) * dpi) For print-3in that is 3 inches, no bleed, 300 DPI, so 900 × 900. The scene has no cut-line vector and no bleed; both outputs are plain PNGs.
From model bytes to a transparent image
When the generation task finishes, tasks._build_files downloads the model's file and calls deliverable.render_outputs with the output specs stored on the task. That function first normalizes orientation and color mode:
base = ImageOps.exif_transpose(Image.open(BytesIO(source))) base = base.convert("RGBA" if base.mode in ("RGBA", "LA", "P") else "RGB") If any output is transparent, it makes one matting call for the whole task and reuses the result for every transparent output:
if needs_bg_removal: bg_removed = await _api_remove_background(_to_png_bytes(base), task_id) if bg_removed is None: logger.warning("bg removal API failed for task %s, transparent outputs will be skipped", task_id) _api_remove_background uploads the source PNG to temporary object storage, gives a signed URL to the hosted matting API, and decodes the PNG that comes back as RGBA. The loop over outputs then reads a small cache keyed by the output's flags, so sticker and print-3in share one prepared image. If matting failed, prepare returns None and each transparent output is skipped with a log line; there is no fallback to a flat JPEG.
The sticker scene does not set "pureWhite", a flag other scenes use to push near-white pixels to exact white. For stickers the white border has to come out of the model's drawing, and the code does nothing to it afterwards.
The resize: _contain for transparent outputs
After matting, each output picks a resize rule:
if spec.get("layout") == "sheet": img = _sheet(_fit(img, spec["itemWidth"], spec["itemHeight"]), spec) elif spec.get("transparent") and img.mode == "RGBA": img = _contain(img, spec["width"], spec["height"]) else: img = _fit(img, spec["width"], spec["height"]) _fit center-crops to the target aspect ratio and scales to the exact size. A crop could cut off a sticker's border, so transparent outputs use _contain:
def _contain(img, width, height): scaled = img.copy() scaled.thumbnail((width, height), Image.Resampling.LANCZOS) if scaled.size == (width, height): return scaled canvas = Image.new("RGBA", (width, height), (0, 0, 0, 0)) x = (width - scaled.width) // 2 y = (height - scaled.height) // 2 canvas.paste(scaled, (x, y)) return canvas The easy-to-miss part is thumbnail: in Pillow it only ever shrinks. If the matted image is smaller than the target, it keeps its size and is centered on a transparent canvas of the target size. So "2048×2048" is the size of the canvas, not necessarily the size of the art.
To see what that means, I extracted _contain from deliverable.py with ast (so I run the real function, not a copy), made synthetic RGBA art that fills 80% of the width like the prompt asks, and passed it through for both output sizes. Source sizes are my choice, because the code does not say what pixel size the 1K setting gives:
| Source image | Art width in the sticker file (2048 canvas) | Art width in the print-3in file (900 canvas) |
|---|---|---|
| 768 × 768 | 615 px | 615 px |
| 1024 × 1024 | 819 px | 725 px |
| 1536 × 1536 | 1229 px | 725 px |
| 2048 × 2048 | 1639 px | 725 px |
| 3072 × 3072 | 1643 px | 725 px |
With a 1024-pixel source, the 2048-pixel sticker file contains art that is 819 pixels wide inside a transparent margin, and the 3-inch print file contains the same art scaled down to 725 pixels. Only a source larger than the target is shrunk. I do not know what size the model returns at 1K, so I cannot say which row applies in production; a quick look at one real result would settle it.
There is an upscaling path in render_outputs: an output with "upscale": true triggers a super-resolution API call. None of the 13 scene files sets it, and for transparent outputs prepare takes the matted image, never the upscaled one, so the two options cannot be combined as the code stands.
Checks you might expect but will not find on the server
render_outputs does not inspect the matted result before it uploads it: no measure of how much of the image is transparent, no fringe check, nothing about the white border. The scene's own spec notes acknowledge the grey plate's side effect: "Designs with very pale grey areas touching the edge of the image may lose them when the background is removed; regenerate if that happens." PiCraft's browser-side image size checker has its own transparency rule (at least 1% transparent pixels for presets that need a transparent background), but that is separate code and is not part of sticker generation.
What I would change
I have not changed any of this.
- Show the art's real pixel size next to "2048×2048", or enlarge the art before placing it. The existing
upscaleflag is one candidate, but it would need to work together with matting. - Log which output ids were skipped when matting fails and surface it in the task result, so the page can say a file is unavailable instead of silently dropping it.
- Turn the table above into a test of
_containwith several source sizes and a non-square source.
What this does not show
- Real matting output. I did not call the hosted API, and nothing here says how well it separates a design from the grey plate.
- The pixel size of a
1Kresult from the model, or how often the model ignores the grey-plate instruction. - Print quality: color accuracy, cutter alignment, or how Cricut Print Then Cut treats the file. Only the dimensions and transparency handling in the code are covered.
- Anything about search rankings. This is about file preparation only.
If you generate die-cut art on a solid plate, do you mat once and resize many ways, or ask the model for alpha every time?
The pipeline above powers the sticker maker.
This article was drafted with help from an AI assistant and edited by me. The code excerpts come from the PiCraft backend repository, and the Pillow measurements are from my own run on 2026-11-04.


