Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Generate an image dynamically in PHP by creating or loading a canvas, drawing data onto it, sending the matching MIME type, and encoding the result with GD or Imagick. The smallest working endpoint uses GD’s imagecreatetruecolor(), color and drawing functions, header('Content-Type: image/png'), and imagepng(). Keep all diagnostics out of the response, validate dynamic input, and save the encoded bytes when you need caching or later reuse.

What a dynamic PHP image endpoint does

A browser requests a normal PHP URL, but the script returns binary image data rather than an HTML document. The endpoint can combine request data, database values, a template, uploaded assets, shapes, and text. The response sequence matters:

  1. Validate dimensions, text, file paths, and any user-controlled options.
  2. Create a blank canvas or load a trusted template.
  3. Allocate colors and draw or composite the dynamic content.
  4. Send the correct Content-Type header before any bytes.
  5. Encode to PNG, JPEG, GIF, WebP, or another format supported by the installed build.
  6. Destroy image objects and return.

Do not place whitespace, a UTF-8 byte-order mark, warnings, or debugging output before the header. A single stray character can corrupt an image.

Check that GD or Imagick is available

Verify GD

GD must be compiled into or enabled in PHP. Run php -m | grep -i gd on Linux/macOS, or inspect phpinfo() on the server. In code, extension_loaded('gd') confirms the extension is loaded. A missing extension causes functions such as imagecreatetruecolor() to be undefined.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Verify capabilities, not just the extension

Use gd_info() to inspect PNG, JPEG, WebP, and FreeType support. Availability depends on how PHP and its underlying libraries were built. FreeType is needed for predictable font rendering with imagefttext().

Install Imagick when needed

Imagick is a PHP extension that exposes ImageMagick operations. Confirm it with extension_loaded('imagick'). ImageMagick policies, resource limits, and supported formats are deployment-specific, so test the exact files and transformations used by your application.

Minimal PNG endpoint with GD

Save this as dynamic-image.php. It streams an 800 × 450 PNG and uses a built-in bitmap font for a dependency-free demonstration.

<?php
declare(strict_types=1);

$im = imagecreatetruecolor(800, 450);
if ($im === false) {
    http_response_code(500);
    exit('Unable to create image');
}

$bg = imagecolorallocate($im, 245, 247, 250);
$fg = imagecolorallocate($im, 25, 35, 45);
$accent = imagecolorallocate($im, 44, 120, 214);

imagefilledrectangle($im, 0, 0, 799, 449, $bg);
imagefilledrectangle($im, 0, 0, 800, 12, $accent);
imagestring($im, 5, 30, 35, 'Runtime generated image', $fg);
imagestring($im, 3, 30, 75, 'Created by PHP GD', $fg);

header('Content-Type: image/png');
header('Cache-Control: public, max-age=300');
imagepng($im);
imagedestroy($im);

Visit the script directly or use it as an <img src="/dynamic-image.php"> source. The encoder writes to the response when no filename is supplied.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Production-quality text with a TrueType font

imagestring() is useful for a quick test but offers limited typography. With FreeType enabled, use imagefttext() and an absolute, trusted font path. Never let a request parameter select an arbitrary filesystem path.

<?php
declare(strict_types=1);

$text = trim((string)($_GET['text'] ?? 'Hello from PHP'));
$text = mb_substr($text, 0, 120);
$font = __DIR__ . '/fonts/DejaVuSans.ttf';
if (!is_file($font) || !is_readable($font)) {
    http_response_code(500);
    exit('Font unavailable');
}

$im = imagecreatetruecolor(1000, 300);
$background = imagecolorallocate($im, 255, 255, 255);
$ink = imagecolorallocate($im, 20, 25, 35);
imagefilledrectangle($im, 0, 0, 999, 299, $background);

$box = imagettfbbox( thirty = 30, 0, $font, $text );
$width = $box[2] - $box[0];
$x = max(20, intdiv(1000 - $width, 2));
imagefttext($im, 30, 0, $x, 160, $ink, $font, $text);

header('Content-Type: image/png');
imagepng($im, null, 6);
imagedestroy($im);

Replace the illustrative thirty = 30 expression with the valid PHP argument 30:

$box = imagettfbbox(30, 0, $font, $text);

Measure text before drawing so it can be centered or wrapped. For multiline text, measure each line and advance the baseline by a chosen line height.

Load templates, resize assets, and preserve transparency

Load a trusted PNG

$template = __DIR__ . '/templates/card.png';
$im = imagecreatefrompng($template);
if ($im === false) {
    http_response_code(500);
    exit('Template could not be loaded');
}
imagealphablending($im, true);
imagesavealpha($im, true);

In PHP 8, successful imagecreatefrompng() calls return a GdImage; failure returns false. Other formats use their corresponding imagecreatefrom* function. Remote URLs may work only when fopen wrappers are enabled; production code should avoid unrestricted remote loading and use an allowlist, size limits, timeouts, and MIME verification.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Composite and resize

$logo = imagecreatefrompng(__DIR__ . '/assets/logo.png');
if ($logo === false) { throw new RuntimeException('Logo failed'); }
$logoW = imagesx($logo);
$logoH = imagesy($logo);
$targetW = 180;
$targetH = (int)round($logoH * $targetW / $logoW);
imagecopyresampled($im, $logo, 40, 40, 0, 0, $targetW, $targetH, $logoW, $logoH);
imagedestroy($logo);

Use imagecopy() for same-size copies and imagecopyresampled() for quality resizing. Keep alpha handling enabled when the output must retain transparency; PNG and WebP can, while JPEG cannot.

Return, save, or cache the result

Stream to the browser

Call the matching encoder after its MIME header: imagepng($im), imagejpeg($im, null, 85), imagegif($im), or a WebP encoder when supported. Match the URL’s advertised type to the encoder.

Save for reuse

$path = __DIR__ . '/cache/card-' . $safeKey . '.png';
if (!imagepng($im, $path, 6)) {
    throw new RuntimeException('Could not write image');
}
imagedestroy($im);

Generate cache keys from normalized inputs (for example, a hash), not raw user text. Write into a non-public temporary file and rename atomically where concurrent requests are possible. Check encoder and filesystem return values.

HTTP caching

For deterministic output, send an ETag or a long Cache-Control lifetime and vary the key when any input changes. For private or user-specific images, use private caching and authorization checks.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

GD, Imagick, or Imagine?

Choice Best fit Trade-offs
GD Common PNG, JPEG, GIF, WebP generation; drawing, thumbnails, text, and template compositing Procedural API; capabilities depend on the PHP build
Imagick ImageMagick-level operations, broad processing, and complex format workflows Requires the extension and ImageMagick policy/resource configuration
Imagine Object-oriented abstraction over GD or Imagick Adds a library layer and driver considerations

There is no authoritative universal speed winner. Benchmark representative dimensions, formats, fonts, and concurrency on your PHP and server stack, while watching memory limits.

Common failures and fixes

  • “Call to undefined function imagecreatetruecolor”. Enable/install GD and restart the PHP worker.
  • Downloaded file is blank or corrupt. Ensure the MIME header precedes output and remove warnings, BOMs, and accidental whitespace.
  • Text is missing. Check FreeType support, use an absolute readable font path, and inspect imagettfbbox() results.
  • “imagecreatefrompng” returns false. Verify the path, permissions, file signature, and memory available for the image.
  • Transparency becomes black. Create an alpha-capable canvas, call imagealphablending($im, false) when appropriate, call imagesavealpha($im, true), and use PNG or WebP.
  • Out-of-memory errors. Reject excessive dimensions, remember that decoded pixels consume far more memory than compressed files, and release intermediate images promptly.
  • Remote asset risks. Do not pass arbitrary URLs to loaders. Allowlist hosts, restrict schemes, set network limits, and validate downloaded bytes.
  • Wrong format or browser behavior. Pair the encoder and Content-Type; do not label JPEG bytes as PNG.

Or skip the browser setup

If your goal is a clean screenshot of a live webpage rather than drawing pixels in PHP, ScreenshotNeo provides a single-call API. It accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.

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

See the ScreenshotNeo API documentation for all options, including PNG/JPEG/WebP, full-page and selector capture, device presets, custom CSS and JavaScript, waits, blocking rules, headers, cookies, geolocation, PDF output, signed links, asynchronous webhooks, bulk capture, caching, and usage reporting.

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)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

The Free plan includes 1,000 shots per month without a card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Security, reliability, and performance checklist

  • Allowlist fonts, templates, and asset directories.
  • Clamp width, height, text length, and color values before allocation.
  • Use strict output buffering and log errors separately from the image response.
  • Set request and execution limits for expensive transformations.
  • Cache deterministic results and include every visual input in the cache key.
  • Test PNG, JPEG, GIF, and WebP outputs with real browsers and image validators.
  • Benchmark on the deployment version; published references do not establish a general throughput number.

Frequently Asked Questions

Can PHP generate an image without writing a file first?

Yes. Send the MIME header and call the encoder without a filename; PHP streams the binary response directly.

Which format should a generated image use?

Use PNG for lossless graphics or transparency, JPEG for photographic output, and WebP when your deployed GD or Imagick build supports it and your clients accept it.

How do I prevent user input from becoming a file or code-injection risk?

Treat text as data, clamp its length, use fixed font and template allowlists, validate paths, and never interpolate untrusted input into PHP, shell commands, or arbitrary URLs.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.