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:
- Validate dimensions, text, file paths, and any user-controlled options.
- Create a blank canvas or load a trusted template.
- Allocate colors and draw or composite the dynamic content.
- Send the correct
Content-Typeheader before any bytes. - Encode to PNG, JPEG, GIF, WebP, or another format supported by the installed build.
- 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.
#1 Best Overall
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.
Rank #2
<?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.
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.
Recommended Free Tools
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.
Rank #4
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.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problemsGD, 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, callimagesavealpha($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.
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →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.
Quick Recap
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.

