Last updated: August 25, 2026.
PHP’s GD extension can create proportional thumbnails for JPEG, PNG, and WebP images. The function below fits an image inside a maximum width and height, preserves transparency where supported, and avoids enlarging small images.
Requirements
- PHP with the GD extension enabled.
- A readable source file and a writable thumbnail directory.
- Enough memory for the decoded image, which can be much larger than the compressed upload.
Create a proportional thumbnail
<?php
function createThumbnail(
string $sourcePath,
string $destinationPath,
int $maxWidth,
int $maxHeight
): void {
if ($maxWidth < 1 || $maxHeight < 1) {
throw new InvalidArgumentException('Thumbnail dimensions must be positive.');
}
if (!is_file($sourcePath) || !is_readable($sourcePath)) {
throw new RuntimeException('Source image is not readable.');
}
if (filesize($sourcePath) > 20 * 1024 * 1024) {
throw new RuntimeException('Source image is too large.');
}
$data = file_get_contents($sourcePath);
$source = $data === false ? false : @imagecreatefromstring($data);
if ($source === false) {
throw new RuntimeException('Unsupported or invalid image.');
}
$sourceWidth = imagesx($source);
$sourceHeight = imagesy($source);
if ($sourceWidth * $sourceHeight > 40_000_000) {
imagedestroy($source);
throw new RuntimeException('Image dimensions are too large.');
}
$scale = min(
$maxWidth / $sourceWidth,
$maxHeight / $sourceHeight,
1
);
$thumbWidth = max(1, (int) floor($sourceWidth * $scale));
$thumbHeight = max(1, (int) floor($sourceHeight * $scale));
$thumbnail = imagecreatetruecolor($thumbWidth, $thumbHeight);
$format = strtolower(pathinfo($destinationPath, PATHINFO_EXTENSION));
if (!in_array($format, ['jpg', 'jpeg', 'png', 'webp'], true)) {
imagedestroy($source);
imagedestroy($thumbnail);
throw new InvalidArgumentException('Use JPG, PNG, or WebP output.');
}
if ($format === 'png' || $format === 'webp') {
imagealphablending($thumbnail, false);
imagesavealpha($thumbnail, true);
$transparent = imagecolorallocatealpha($thumbnail, 0, 0, 0, 127);
imagefill($thumbnail, 0, 0, $transparent);
} else {
$white = imagecolorallocate($thumbnail, 255, 255, 255);
imagefill($thumbnail, 0, 0, $white);
}
imagecopyresampled(
$thumbnail,
$source,
0,
0,
0,
0,
$thumbWidth,
$thumbHeight,
$sourceWidth,
$sourceHeight
);
$saved = match ($format) {
'jpg', 'jpeg' => imagejpeg($thumbnail, $destinationPath, 85),
'png' => imagepng($thumbnail, $destinationPath, 6),
'webp' => imagewebp($thumbnail, $destinationPath, 82),
};
imagedestroy($source);
imagedestroy($thumbnail);
if (!$saved) {
throw new RuntimeException('The thumbnail could not be saved.');
}
}imagecopyresampled() performs smooth resampling, which produces better reduced images than a simple pixel resize. The destination file extension selects JPG, PNG, or WebP output.
Call the function
<?php
$source = __DIR__ . '/uploads/photo.jpg';
$destination = __DIR__ . '/thumbnails/photo.webp';
createThumbnail($source, $destination, 480, 320);The result fits inside 480 × 320 pixels while retaining its aspect ratio. Create the destination directory before calling the function and give the PHP process permission to write there.
Handling uploaded images safely
- Enforce both compressed file-size and decoded pixel limits.
- Use Fileinfo and an actual image decoder; do not trust the browser-provided MIME type or filename extension.
- Generate destination filenames on the server.
- Store uploads where PHP files cannot execute.
- Reject unreadable, unsupported, or corrupt files without displaying internal paths to visitors.
Phone-photo orientation
JPEG photos may store rotation in EXIF metadata. If phone uploads appear sideways, read the orientation with exif_read_data() and rotate the source before calculating the thumbnail dimensions.
Useful GD references
imagecreatefromstring()decodes supported image data.imagecopyresampled()resizes with interpolation.- PHP GD function reference lists supported formats and related functions.
This works great thanks.
It does create very low quality thumbnails though. How would I go about creating better quality ones? It seems the resizing algorithm here is a basic pixel resize one rather than the ones that give smoother results.