ai newsDi., 20. Okt.
EilmeldungCan we detect harmful AI before it's too late? Researchers search for warning signs

AI Edits the Art, Code Owns the Pixels: Shipping OG Images and Store Screenshots for Side Projects

Every side project hits the same wall right before launch. The code works, the landing page is up, and then you remember you still need: an Open Graph image so your link doesn't show up as a grey box on X, LinkedIn, Slack and Discord, App Store screenshots in the sizes Apple wants this year, Google

dev.toDienstag 11:48 · 20.10.7 Min. Lesezeit
Bild: dev.to

Every side project hits the same wall right before launch. The code works, the landing page is up, and then you remember you still need:

AI image editors have made the "make it look nice" part much faster. But I've seen (and made) a lot of assets where the AI part went fine and the boring part went wrong: text cropped by X's 2:1 crop, PNGs rejected for having an alpha channel, a mascot that looks like a different animal in every screenshot.

This post is the workflow I've settled on. For the hands-on steps I'll use Nano Banana Pro, an AI image editor built on Google's Nano Banana models, because it handles reference images and plain-language edits well. The rule that makes it work is simple:

The AI edits the art. Code owns the pixels.

The model handles backgrounds, lighting, mascots and mood. A script handles exact dimensions, real text, file format and validation. They're kept apart because they fail in different ways.

Here's what the official docs say as of October 2026. Re-check them before you ship, because Apple in particular changes its required sizes every year or so.

A 1200×630 image works fine on X, but X crops it to 2:1. So keep text and logos away from the top and bottom ~30px, and put the important stuff in the center.

Apple App Store ( Screenshot specifications )

Two things trip people up all the time: alpha channels (most AI tools and design exports produce RGBA PNGs) and aspect ratios that are close but not exact. The script below fixes both.

Consistency doesn't come from a clever prompt. It comes from reusing the same inputs. Before touching an AI editor, make a brand/ folder:

brand/ palette.json # 3–5 hex colors, which one is the background, which one is the accent mascot-ref-01.png # 2–4 clean reference images of your mascot/character mascot-ref-02.png style.md # one paragraph: "soft studio lighting, flat pastel backdrop,..." fonts/ # the font you'll use for headlines (for the script, not the AI) Enter fullscreen mode Exit fullscreen mode If you have a mascot, the reference images matter most. Nano Banana Pro is built for character consistency: the underlying model accepts up to 14 reference images, and you can upload up to 10 per edit in the editor. So give it the same 2–4 references every time, and don't keep re-generating your mascot "from the description." When you get an output you like, add it to brand/ and use it as a reference next time.

Write the style.md paragraph once and paste it into every edit. Do the same with negative instructions ("no text, no logos, no UI elements").

App Store and Play reviewers expect screenshots to show the actual app. So the UI itself should never be AI-generated or AI-"enhanced." Capture it on a simulator or emulator at native resolution, with:

Use the AI editor for the parts that surround the UI: backgrounds, the mascot pointing at a feature, lighting, texture, seasonal variants. Generate these as separate layers at a larger size than you need, with empty space where the device frame and headline will go.

Here's how I do it in the image-to-image editor:

If an older asset or a hand-drawn mascot sketch is too small to crop from, run it through the AI image upscaler (2×–4×, inputs up to 2 megapixels) first instead of stretching it in code. For a mascot cut-out on a plain background, the background remover on the same site gives you a clean subject to drop into new scenes.

Edit instructions that work well for this kind of asset tend to be:

Download the results into art/ and hand them to the script.

This is where the "code owns the pixels" part comes in. Here's a small Node script using sharp. It takes an AI-generated background, places your real screenshot and a headline on top, and exports every target size with no alpha channel.

// make-assets.mjs — npm i sharp import sharp from " sharp "; import fs from " node:fs/promises "; const TARGETS = { og: { w: 1200, h: 630 }, playFeature: { w: 1024, h: 500 }, iphone63: { w: 1206, h: 2622 }, // Dynamic Island (medium display) ipad13: { w: 2064, h: 2752 }, playPhone: { w: 1080, h: 1920 }, // 9:16, within Play's 2:1 rule }; const BG = " #0f766e "; // fallback / flatten color from palette.json function headlineSvg ( text, w, h, fontSize ) { // Real text rendered by code: localizable, typo-fixable, always sharp. const safe = text. replace ( /&/g, " & " ). replace ( /</g, " < " ); return Buffer. from ( ` <svg width=" ${ w } " height=" ${ h } " xmlns="http://www.w3.org/2000/svg"> <text x="50%" y=" ${ Math. round ( h * 0.12 )} " text-anchor="middle" font-family="Inter, sans-serif" font-weight="700" font-size=" ${ fontSize } " fill="#ffffff"> ${ safe } </text> </svg>` ); } async function build ( name, { w, h }, { background, screenshot, headline }) { const base = await sharp ( background ). resize ( w, h, { fit: " cover ", position: " attention " }) // crop toward the interesting area. toBuffer (); const layers = []; if ( screenshot && h > w ) { // Portrait store shot: real UI at ~70% height, bottom-centered. const shotH = Math. round ( h * 0.7 ); const shot = await sharp ( screenshot ). resize ({ height: shotH }). toBuffer (); const { width: shotW } = await sharp ( shot ). metadata (); layers. push ({ input: shot, top: h - shotH - Math. round ( h * 0.04 ), left: Math. round (( w - shotW ) / 2 ) }); } if ( headline ) { layers. push ({ input: headlineSvg ( headline, w, h, Math. round ( w * 0.07 )), top: 0, left: 0 }); } await sharp ( base ). composite ( layers ). flatten ({ background: BG }) // kills the alpha channel stores reject. png ({ compressionLevel: 9 }). toFile ( `out/ ${ name }.png` ); } await fs. mkdir ( " out ", { recursive: true }); for ( const [ name, size ] of Object. entries ( TARGETS )) { await build ( name, size, { background: " art/bg-wide.png ", // AI-generated, larger than any target screenshot: " raw/home-light.png ", // real simulator capture headline: name === " og "? " Ship your side project faster ": " Plan your week in 30 seconds ", }); } Enter fullscreen mode Exit fullscreen mode It's deliberately simple. In a real project I keep headlines in a copy.json keyed by locale and loop over it, so one change regenerates 10 languages × 5 sizes.

Prefer Python? The same idea in Pillow is Image.open(bg).convert("RGB") (that drops alpha), then ImageOps.fit(img, (w, h)) for a cover crop, then ImageDraw.text() for the headline.

Store upload errors are slow to debug through a web console. A ten-line check catches most of them:

// check-assets.mjs import sharp from " sharp "; import { readdir } from " node:fs/promises "; for ( const f of await readdir ( " out " )) { const m = await sharp ( `out/ ${ f } ` ). metadata (); const long = Math. max ( m. width, m. height ), short = Math. min ( m. width, m. height ); const problems = []; if ( m. hasAlpha ) problems. push ( " has alpha " ); if ( long > 2 * short ) problems. push ( " ratio > 2:1 (Play rejects) " ); if ( f. startsWith ( " og " ) && ( m. size?? 0 ) > 5 * 1024 * 1024 ) problems. push ( " > 5 MB " ); console. log ( ` ${ f }: ${ m. width } x ${ m. height } ${ problems. length? " ❌ " + problems. join ( ", " ): " ✅ " } ` ); } Enter fullscreen mode Exit fullscreen mode For OG images, also paste the live URL into each platform's own card or post inspector after deploying.

Caches are aggressive, and that's the only way to see what people will actually see.

That's the whole pipeline. Setting it up takes an afternoon, and after that, a new screenshot set or a seasonal OG image is one edit plus one node make-assets.mjs.

If you have a smarter way to handle localized screenshots or device frames, I'd love to hear it in the comments.

Quelle: dev.to (dev.to). Original öffnen