AI 編輯圖像、程式控制像素:個人項目的 OG 圖片與商店截圖製作流程
AI Edits the Art, Code Owns the Pixels: Shipping OG Images and Store Screenshots for Side Projects
Yosta 分享為個人項目製作 OG 圖片及 App Store、Google Play 商店截圖的流程,將 AI 生成圖像與程式合成真實介面和文字分開處理。流程以 Nano Banana Pro 生成背景和吉祥物等素材,並以 1200×630 製作 OG 圖片、以 1024×500 製作 Google Play feature graphic。
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 Play screenshots plus a feature graphic,
- and ideally all of it looking like it came from the same product.
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.
1. Get the specs right first (they change)
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.
Open Graph / social cards
| Where | What to target |
|---|---|
og:image (Facebook, LinkedIn, Slack, Discord) |
1200×630 (1.91:1) |
X summary_large_image
|
2:1 ratio, min 300×157, max 4096×4096, under 5 MB, no SVG |
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)
- 1–10 screenshots per device size,
.jpeg/.jpg/.png, no alpha channel or transparency. - Required for iPhone: at least one screenshot for iPhone with Dynamic Island (medium display), which is 1206×2622 or 1179×2556 portrait.
- Required if you support iPad: the 13" display, 2064×2752 or 2048×2732 portrait.
- Larger iPhone classes (for example 1320×2868) are accepted. App Store Connect scales from the sizes you provide.
Google Play (Preview assets)
- Screenshots: JPEG or 24-bit PNG (no alpha). Each side 320–3840px, and the long side can't be more than 2× the short side. Minimum 2, up to 8 per device type.
- Feature graphic (required): exactly 1024×500, JPEG or 24-bit PNG, no alpha. Keep important content centered because it gets cropped in some placements.
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.
2. Build a tiny brand kit before generating anything
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)
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").
3. The workflow: raw screenshot → store-ready asset
Step 1: Capture clean, real UI
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:
- realistic but fake demo data (no real emails or names),
- a clean status bar (on iOS,
xcrun simctl status_bar booted override --time 9:41 --batteryState charged --batteryLevel 100), - light and dark variants if you plan to use both.
Step 2: Generate the scene around it in Nano Banana Pro
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:
- Choose Nano Banana Pro as the model and upload your mascot references from
brand/, plus an existing background if you're iterating on one. - Describe the edit in plain language: paste your
style.mdparagraph, then one specific change (examples below). - Pick the aspect ratio closest to the asset you're making (16:9 for OG and feature graphics, 9:16 for phone screenshots) and set the output to 2K or 4K. The script crops from this, so extra resolution helps.
- Iterate one edit at a time. Swap the background, change the lighting, move the mascot, but keep each pass to a single change so you can tell which instruction did what.
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:
- Specific about what to keep: "Keep the mascot's face, colors and proportions exactly as in the reference images."
- Specific about one change: "Change only the background to a soft teal-to-white gradient with gentle studio lighting from the top left." One change per pass beats five changes in one prompt.
- Specific about empty space: "Leave the right 45% of the frame empty and uncluttered for a phone mockup."
- Explicit about no text: "No text, letters, logos, or watermarks." Nano Banana Pro renders text well, but your headline still belongs in code, where you can fix a typo, localize it into 10 languages, and know it's pixel-sharp.
Download the results into art/ and hand them to the script.
Step 3: Composite and resize in code
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",
});
}
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.
Step 4: Validate before you upload
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(", ") : "✅"}`);
}
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.
4. Common mistakes
- Letting the AI redraw your UI. It will "improve" a button label into gibberish, or invent a feature you don't have. Reviewers notice, and so do users.
- Baking the headline into the generated image. You'll find a typo after uploading 30 localized screenshots.
-
Generating at the exact target size. Generate bigger and crop in code.
fit: "cover"withposition: "attention"usually keeps the interesting part. - Ignoring the X crop. Anything within ~30px of the top or bottom of a 1200×630 card can disappear.
- A new mascot every time. Same reference images, same style paragraph, and reuse the best output as a reference.
-
Exporting RGBA PNGs. Always
flatten()/convert("RGB")for store assets. - Using one generic OG image for every page. X's docs specifically say not to use a generic site-wide image. A per-page template with a different headline is cheap with the script above.
5. Pre-launch checklist
- [ ] Specs re-checked against Apple and Google docs this month
- [ ]
brand/folder: palette, 2–4 mascot references, style paragraph, font - [ ] Real UI captured at native resolution, clean status bar, fake demo data
- [ ] Backgrounds, mascot and lighting generated at 2K/4K from the same references, with "no text" in every instruction
- [ ] Headlines rendered by code from a copy file (localizable)
- [ ] All store assets flattened (no alpha), ratios verified by script
- [ ] OG image 1200×630 with key content centered, plus
og:image:width/height/alttags - [ ] Live link previews checked in each platform's inspector
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.
來源:DEV Community · dev.to
