ScreenshotNeo

BlogHow-to

How to Generate Images From Code

Learn when to draw images deterministically, when to call a generative API, and how to build reliable Python, JavaScript, shell, and browser workflows.

By the ScreenshotNeo team29 September 20269 min read

How to Generate Images From Code

Generating an image from code can mean two different things:

  1. Drawing or compositing deterministically: your program places pixels, shapes, text, and existing assets exactly where you specify.
  2. Generating or editing with an image model: your program sends a prompt and optional image inputs to a hosted service that returns new image data.

Choose the first approach for repeatable layouts, charts, badges, thumbnails, and branded graphics. Choose the second when you need semantic synthesis, such as creating a scene from a natural-language description or editing an image according to instructions. This guide shows both paths with runnable examples in Python, JavaScript, the browser, and the shell.

1. Choose the right image-generation workflow

Start with the output you need rather than the library you already know.

Code can draw pixels locally, compose them in a browser, process them in a shell, or request synthesis from an image API.
Code can draw pixels locally, compose them in a browser, process them in a shell, or request synthesis from an image API.
Need Good starting point Why
Precise text, shapes, overlays, or compositing in Python Pillow and ImageDraw Coordinates and pixels are deterministic, and transparent layers can be alpha-composited.
Drawing or composing in a web page HTML Canvas The browser can draw programmatic and loaded image sources with drawImage().
Batch conversion, resizing, effects, or command-line drawing ImageMagick The magick command handles common formats and vector drawing workflows.
A new scene from a prompt or an image-guided edit A hosted image API The provider performs semantic synthesis and returns image bytes or encoded data.

Also compare the environment, required input formats, transparency needs, operational constraints, and whether you need exact repeatability. The sources do not establish that one hosted provider is universally faster, cheaper, or better.

2. Generate a deterministic image with Python and Pillow

Pillow’s ImageDraw API draws onto an image in place. Install it in an isolated environment:

python -m venv .venv
. .venv/bin/activate
python -m pip install Pillow

The following complete script creates a 1200×630 PNG, draws a background, rounded card, lines, and text, then saves it. The font path is intentionally configurable because TrueType support and installed fonts vary by environment.

from pathlib import Path
from PIL import Image, ImageDraw, ImageFont

WIDTH, HEIGHT = 1200, 630
out = Path("generated-card.png")

image = Image.new("RGBA", (WIDTH, HEIGHT), (15, 23, 42, 255))
draw = ImageDraw.Draw(image)

# A deterministic gradient made from horizontal bands.
for y in range(HEIGHT):
    t = y / (HEIGHT - 1)
    color = (15, int(23 + 35 * t), int(42 + 55 * t), 255)
    draw.line((0, y, WIDTH, y), fill=color)

# Accent shapes.
draw.ellipse((860, -140, 1320, 320), fill=(56, 189, 248, 90))
draw.rounded_rectangle((90, 100, 1110, 530), radius=28,
                       fill=(30, 41, 59, 235), outline=(71, 85, 105, 255), width=3)

def load_font(size):
    candidates = [
        "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
        "/Library/Fonts/Arial.ttf",
        "C:/Windows/Fonts/arial.ttf",
    ]
    for name in candidates:
        if Path(name).exists():
            return ImageFont.truetype(name, size)
    return ImageFont.load_default()

title_font = load_font(64)
body_font = load_font(30)
small_font = load_font(22)

draw.text((150, 165), "Generated by code", font=title_font,
          fill=(248, 250, 252, 255))
draw.text((150, 260), "Deterministic pixels, repeatable output", font=body_font,
          fill=(186, 230, 253, 255))
draw.text((150, 430), "Pillow + ImageDraw", font=small_font,
          fill=(148, 163, 184, 255))

image.save(out, format="PNG")
print(f"wrote {out}")

For compositing, create a transparent RGBA layer, draw on that layer, and combine it with the base:

overlay = Image.new("RGBA", image.size, (0, 0, 0, 0))
overlay_draw = ImageDraw.Draw(overlay)
overlay_draw.rounded_rectangle((80, 80, 500, 220), radius=20,
                               fill=(255, 255, 255, 70))
composited = Image.alpha_composite(image, overlay)
composited.save("composited.png")

Keep layout inputs such as colors, coordinates, and font sizes in configuration rather than scattering them through drawing code. That makes batch generation and visual regression checks easier. If text differs across machines, install and pin a font file instead of relying on a system default.

3. Generate images in the browser with Canvas

Canvas is useful when the image must be produced in a browser, downloaded by a user, or combined with browser-only data. drawImage() accepts image sources such as an HTMLImageElement, a video frame, another canvas, or an image bitmap.

<canvas id="poster" width="1200" height="630"></canvas>
<button id="download">Download PNG</button>
<script>
const canvas = document.querySelector('#poster');
const ctx = canvas.getContext('2d');

ctx.fillStyle = '#0f172a';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#38bdf8';
ctx.beginPath();
ctx.arc(1010, 90, 180, 0, Math.PI * 2);
ctx.fill();

ctx.fillStyle = '#f8fafc';
ctx.font = 'bold 64px system-ui, sans-serif';
ctx.fillText('Generated by code', 110, 220);
ctx.fillStyle = '#bae6fd';
ctx.font = '30px system-ui, sans-serif';
ctx.fillText('Canvas composition', 110, 285);

// A programmatically created source can be drawn like any other source.
const badge = document.createElement('canvas');
badge.width = 280;
badge.height = 90;
const badgeCtx = badge.getContext('2d');
badgeCtx.fillStyle = '#1e293b';
badgeCtx.fillRect(0, 0, badge.width, badge.height);
badgeCtx.fillStyle = '#e2e8f0';
badgeCtx.font = '24px system-ui, sans-serif';
badgeCtx.fillText('Canvas source', 24, 55);
ctx.drawImage(badge, 110, 410);

document.querySelector('#download').addEventListener('click', () => {
  const link = document.createElement('a');
  link.download = 'poster.png';
  link.href = canvas.toDataURL('image/png');
  link.click();
});
</script>

External images are subject to browser cross-origin rules. If you draw an image from another origin without the appropriate CORS response, the canvas can become “tainted,” preventing export with toDataURL() or toBlob(). Configure the image host and server headers, proxy the asset through a permitted origin, or use same-origin files.

4. Use ImageMagick for shell and batch workflows

ImageMagick’s magick command can create canvases, draw, resize, convert, and apply effects. A simple generated SVG can be rendered to PNG:

cat > card.svg <<'SVG'
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630">
  <rect width="1200" height="630" fill="#0f172a"/>
  <circle cx="1010" cy="90" r="180" fill="#38bdf8" opacity=".55"/>
  <rect x="90" y="100" width="1020" height="430" rx="28" fill="#1e293b"/>
  <text x="150" y="240" fill="#f8fafc" font-size="64" font-family="sans-serif">Generated by code</text>
  <text x="150" y="315" fill="#bae6fd" font-size="30" font-family="sans-serif">ImageMagick rendering</text>
</svg>
SVG
magick card.svg card.png

You can also create and annotate a raster image directly:

magick -size 1200x630 xc:'#0f172a' \
  -fill '#38bdf8' -draw 'circle 1010,90 1010,270' \
  -fill '#f8fafc' -pointsize 64 -draw 'text 150,240 "Generated by code"' \
  output.png

For complex vector graphics, generating SVG is often easier to maintain than hand-authoring MVG commands. Check your installed ImageMagick version and enabled delegates before depending on a particular format.

5. Generate or edit an image with a hosted API

Use an image API when the desired result is semantic rather than geometric. A typical integration sends a prompt, optional source images, and output settings, then decodes the returned bytes or base64 data. Keep API keys in environment variables and validate the returned MIME type before writing a file.

OpenAI’s documentation distinguishes a single image request from a conversational, multi-step workflow: “If you only need to generate or edit a single image from one prompt, the Image API is your best choice.” Its documented controls include size, quality, format, compression, and background for supported models. PNG is the default in the Image API documentation; JPEG and WebP, plus transparent backgrounds, depend on model and format support. Verify current model identifiers, dimensions, eligibility, and limits in the provider reference before deploying.

Google’s Gemini API documentation provides Python and JavaScript examples that request image output, decode the returned data, and write a PNG. The exact SDK methods and model names can change, so use the current provider examples rather than copying an old endpoint.

Python request pattern

import base64
import os
from pathlib import Path

# Adapt this structure to the current provider SDK and response schema.
# Keep the key outside source control.
api_key = os.environ['IMAGE_API_KEY']
prompt = 'A clean editorial illustration of a robot assembling pixels from code'

# The provider call belongs here. Read the current image API guide for
# authentication, model name, size, and returned data fields.
result = call_image_provider(api_key=api_key, prompt=prompt)
image_bytes = base64.b64decode(result['image_base64'])
Path('generated.png').write_bytes(image_bytes)

JavaScript request pattern

const apiKey = process.env.IMAGE_API_KEY;
if (!apiKey) throw new Error('Set IMAGE_API_KEY');

const prompt = 'A clean editorial illustration of a robot assembling pixels from code';
// Use the current provider SDK or documented HTTP endpoint here.
const result = await callImageProvider({ apiKey, prompt });
const bytes = Buffer.from(result.image_base64, 'base64');
await import('node:fs/promises').then(fs => fs.writeFile('generated.png', bytes));

Production code should handle timeouts, rate limits, transient 5xx responses, malformed responses, content filtering or policy errors, and oversized outputs. Record the provider request ID when one is returned. Do not assume that a setting accepted by one model is accepted by another.

6. Make output predictable and maintainable

  • Separate specification from rendering: represent text, positions, colors, and assets as data.
  • Control dimensions explicitly: define width, height, device pixel ratio, and output format.
  • Use stable fonts: package licensed font files when text must match across workers.
  • Choose alpha deliberately: use RGBA for transparency and verify that the target format preserves it.
  • Validate inputs: reject impossible dimensions, untrusted file paths, and unexpectedly large uploads.
  • Make jobs idempotent: derive an output key from normalized inputs so retries do not create duplicates.
  • Store metadata: keep the prompt or drawing specification, model/library version, dimensions, and output MIME type with the asset.

7. Troubleshooting common failures

Symptom Likely cause Fix
Text is missing or rendered differently Font unavailable or unsupported glyphs Install/package a known TrueType or OpenType font and verify its license.
Canvas export throws a security error Cross-origin image tainted the canvas Use same-origin assets, configure CORS, or proxy the image.
Transparent output becomes black Opaque background or format conversion discarded alpha Use RGBA throughout and select PNG or a format that preserves transparency.
ImageMagick cannot read SVG Missing delegate or security policy restriction Inspect magick -version, install the required delegate, and review policy settings.
Hosted API returns 400 Unsupported model, size, format, or missing field Compare the request with the current provider reference and remove unsupported options.
Hosted API times out Large image, queueing, or network timeout Set a bounded client timeout, retry transient failures with backoff, and avoid unbounded parallelism.
Retries create duplicate files No idempotency key or deterministic output name Hash normalized inputs and write atomically to a known destination.

8. Performance, reliability, and cost

Deterministic local drawing is usually easiest to scale horizontally: workers can render independently, and output size is predictable. Memory use grows with pixel count, color depth, and the number of simultaneously open images. Close files promptly and process large batches incrementally.

Canvas work runs on the browser’s main thread unless moved to a worker with an offscreen canvas. Large images and repeated pixel operations can block interaction. ImageMagick batch jobs benefit from bounded concurrency and temporary-file cleanup.

Hosted generation adds network latency, provider quotas, model-specific limits, and per-request charges. Cache results by a normalized prompt, input-image digest, and settings when your use case permits. Track successful and failed requests separately, and make retries conditional: retry timeouts and transient server errors, but fix authentication, validation, and policy errors instead.

9. Or skip the browser setup

If your goal is to capture an existing web page as an image rather than synthesize pixels, ScreenshotNeo provides a website screenshot API and MCP server. It accepts one GET request and returns PNG, JPEG, WebP, or PDF. The DIY browser workflow often needs cookie handling, popup removal, waits, viewport configuration, and failure detection; ScreenshotNeo exposes those as API options.

A capture service can prepare a page, remove obstructive elements, and return an image or PDF.
A capture service can prepare a page, remove obstructive elements, and return an image or PDF.

cURL: see the ScreenshotNeo docs for all options.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python:

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Cookie banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed; response headers identify the page verdict and whether it was billed. An MCP server lets Claude, Cursor, and other MCP clients call take_screenshot, get_page_info, and capture_pdf. The Free plan includes 1,000 screenshots each month with no card, and paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

10. FAQ

Should I draw with code or use an image model?

Draw when geometry, text, and repeatability matter. Use a model when the content requires semantic synthesis or image-guided editing.

Which format should I save?

Use PNG for lossless graphics and transparency, JPEG for photographic images without transparency, and WebP when your consumers support it and smaller files are useful.

Can I generate images without a server?

Yes. Pillow, Canvas, and ImageMagick can run locally or in your own workers. Hosted APIs require network access and credential management.

How do I keep generated assets reproducible?

Pin library or model versions where possible, package fonts, record all inputs and settings, and use deterministic seeds only when the provider documents seed support.

What is the safest way to process user supplied images?

Validate file type and dimensions, isolate processing workers, enforce size and time limits, and avoid treating image metadata or filenames as trusted input.