Create Image Thumbnails with PHP

Published Updated

A thumbnail needs its own smaller file instead of a CSS box around the full-size asset. A current PHP app should create thumbnail assets at upload time, import time, or in a background job, then serve those generated files through ordinary HTML.

Think of the original as a master negative and each thumbnail as a print made for one frame. Keep the master, name each print by its intended size, and regenerate those prints when the frame changes.

Use PHP for the file-generation step, then let the browser handle layout, srcset, lazy loading, and display rules. Mixing those jobs is how a small image feature becomes a slow gallery and, if the path handling is loose, a file-disclosure bug with nice square corners.

These examples require the PHP GD extension with JPEG and PNG support. Check extension_loaded("gd") and gd_info() in the same PHP environment that runs the image job. Explicit GdImage type hints require PHP 8.0 or newer, while the promoted readonly properties require PHP 8.1 or newer.

Keep the original image and generate each thumbnail size from it, so later changes do not compound earlier compression.

Read this after working with text files in PHP if your app now needs to create real image files. The HTML tutorials hub covers the stable markup and responsive display rules that generated files still need.

Which Thumbnail Sizes Should You Generate?

Generate the sizes your interface uses. A gallery might keep one original and two thumbnail files:

storage/
  originals/
    product-1329.jpg
  thumbnails/
    product-1329-320.jpg
    product-1329-640.jpg

The original file stays available for a detail view, download, or later regeneration. The thumbnail files are cheap to serve in the grid. If the design changes from 240-pixel cards to 320-pixel cards, regenerate the derivative files from the original. Do not keep resampling thumbnails from thumbnails until the image has been through the washing machine a few times.

For a small PHP app, GD is still a reasonable teaching tool. If the site is image-heavy, needs WebP/AVIF pipelines, or does a lot of editorial cropping, you eventually want a dedicated image library, a queue worker, or an image service. But the geometry is the same either way: load a source, calculate dimensions, resample into a destination, save the derivative.

How Should PHP Choose the Source Image?

PHP should look up the image by an application ID and use its stored server-side path. Accepting an image path from the query string and stripping .. out of it is still a common move, and it is exactly the sort of thing that should make you uneasy. Removing one substring leaves the request in control of the path.

The safer move is to use an application identifier:

<?php

final class Photo
{
    public function __construct(
        public readonly int $id,
        public readonly string $originalPath,
        public readonly string $thumbnailBaseName,
    ) {
    }
}

The controller should look up Photo by ID, then pass a known server-side path into the thumbnail job. The user should never get to decide that the image path is ../../config.php, a private upload, or a file outside the upload root. That is the unglamorous part of image work, and it matters more than the resize formula.

How Do You Create a Proportional PHP Thumbnail?

Create a proportional PHP thumbnail by calculating one scale factor for both dimensions and passing the result to imagecopyresampled(). The function below fits the full image inside a maximum box without cropping. That is the safer default for product photos, screenshots, diagrams, and anything where cutting off an edge loses information.

<?php

function createJpegThumbnail(
    string $sourcePath,
    string $targetPath,
    int $maxWidth = 320,
    int $maxHeight = 320,
): void {
    if ($maxWidth < 1 || $maxHeight < 1) {
        throw new InvalidArgumentException("Thumbnail dimensions must be positive.");
    }

    $size = getimagesize($sourcePath);

    if ($size === false) {
        throw new RuntimeException("The source file is not a readable image.");
    }

    [$sourceWidth, $sourceHeight, $imageType] = $size;

    if ($imageType !== IMAGETYPE_JPEG) {
        throw new RuntimeException("This thumbnail job expects a JPEG source.");
    }

    $source = imagecreatefromjpeg($sourcePath);

    if ($source === false) {
        throw new RuntimeException("Could not load the source JPEG.");
    }

    $scale = min(
        $maxWidth / $sourceWidth,
        $maxHeight / $sourceHeight,
        1,
    );

    $thumbWidth = max(1, (int) floor($sourceWidth * $scale));
    $thumbHeight = max(1, (int) floor($sourceHeight * $scale));

    $thumb = imagecreatetruecolor($thumbWidth, $thumbHeight);

    imagecopyresampled(
        $thumb,
        $source,
        0,
        0,
        0,
        0,
        $thumbWidth,
        $thumbHeight,
        imagesx($source),
        imagesy($source),
    );

    if (! imagejpeg($thumb, $targetPath, 82)) {
        throw new RuntimeException("Could not write the thumbnail file.");
    }

}

This helper accepts JPEG input and never enlarges a smaller source. Add other formats only when the GD build supports them, and preserve transparency for PNG or WebP output. PHP 8 releases GD image objects when no references remain; imagedestroy() is unnecessary and deprecated as of PHP 8.5.

The imagecopyresampled() call is the current GD workhorse here. It copies a rectangular region from the source into a destination image and interpolates pixels during the resize, which is the quality difference you care about when reducing a photo.

How Do You Crop a Square Thumbnail?

Create a square thumbnail by cropping a square region from the source and resizing that region without stretching it. If every gallery card must be square, crop from the center and be honest that the edges may disappear:

<?php

function createSquareJpegThumbnail(
    string $sourcePath,
    string $targetPath,
    int $size = 320,
): void {
    if ($size < 1) {
        throw new InvalidArgumentException("Thumbnail size must be positive.");
    }

    $source = imagecreatefromjpeg($sourcePath);

    if ($source === false) {
        throw new RuntimeException("Could not load the source JPEG.");
    }

    $sourceWidth = imagesx($source);
    $sourceHeight = imagesy($source);
    $side = min($sourceWidth, $sourceHeight);
    $sourceX = intdiv($sourceWidth - $side, 2);
    $sourceY = intdiv($sourceHeight - $side, 2);
    $scale = min($size / $side, 1);
    $targetSize = max(1, (int) floor($side * $scale));

    $thumb = imagecreatetruecolor($targetSize, $targetSize);

    imagecopyresampled(
        $thumb,
        $source,
        0,
        0,
        $sourceX,
        $sourceY,
        $targetSize,
        $targetSize,
        $side,
        $side,
    );

    if (! imagejpeg($thumb, $targetPath, 82)) {
        throw new RuntimeException("Could not write the thumbnail file.");
    }

}

That is a clean crop, but it is still blind. Faces, logos, screenshots, and charts do not always sit in the center. In a real CMS, store a focal point or let an editor choose the crop. If that sounds like more product work than you wanted, that is the clue. Use proportional thumbnails and let CSS handle the display box.

When Should PHP Generate Thumbnails?

PHP should generate thumbnails at upload time, import time, or in a background job, once per source image and target size. The request that shows the gallery should not be resizing images for every visitor.

<?php

$photo = new Photo(
    id: 1329,
    originalPath: __DIR__ . "/../storage/originals/product-1329.jpg",
    thumbnailBaseName: "product-1329",
);

createJpegThumbnail(
    $photo->originalPath,
    __DIR__ . "/../public/uploads/thumbs/{$photo->thumbnailBaseName}-320.jpg",
    320,
    320,
);

createJpegThumbnail(
    $photo->originalPath,
    __DIR__ . "/../public/uploads/thumbs/{$photo->thumbnailBaseName}-640.jpg",
    640,
    640,
);

If the image came from an upload form, validate the upload before this step. Check the upload error code, cap the file size, store the original under a generated name, keep private originals out of the public directory when needed, and run image inspection on the server. The PHP manual's upload examples are deliberately low-level; your application still owns the storage policy.

The master-negative rule keeps this regeneration predictable. Every 320-pixel or 640-pixel derivative comes from the accepted original, never from another thumbnail that has already lost detail.

How Do You Display PHP Thumbnails in HTML?

Once PHP has generated the files, the page should point at the thumbnail files directly:

<img
  src="/uploads/thumbs/product-1329-320.jpg"
  srcset="/uploads/thumbs/product-1329-320.jpg 320w,
          /uploads/thumbs/product-1329-640.jpg 640w"
  sizes="(width < 700px) 45vw, 220px"
  width="320"
  height="320"
  loading="lazy"
  decoding="async"
  alt="Walnut desk organizer with two notebooks">

The example assumes square source files at least 640 pixels wide. For proportional thumbnails, set each srcset width descriptor and the image width and height to the actual generated dimensions. PHP made the derivative files, HTML tells the browser which assets exist, and CSS controls cropping, borders and layout. The HTML loading attribute requests lazy loading.

Do not route every thumbnail request through thumbnail.php unless you have a measured reason. A generated file can be cached by the browser, CDN, or static server. A PHP thumbnail endpoint has to solve caching, validation, path safety, and error handling on every request, which is worth it in a few cases but usually overbuilt for a basic gallery.

Common Pitfalls & Debugging

Large Uploads Exhaust Memory

A large photo passes the file-size check but thumbnail generation terminates. Decoded pixel data needs far more memory than the compressed upload. Inspect dimensions before decoding, reject images above the application's pixel limit, and move heavier jobs away from the request path.

PNG Transparency Becomes Black

A transparent PNG gets a black background after resizing. The destination image was created without alpha preservation and a transparent fill. Configure alpha on the destination before resampling, or keep the JPEG-only boundary shown in this tutorial until PNG output is implemented deliberately.

The Target File Never Appears

No thumbnail is written even though the resize calculations finish. The destination directory is absent, the PHP process cannot write there, or imagejpeg() returned false. Create and own the directory during deployment, then throw when the encoder reports failure.

Frequently Asked Questions

Is WebP always smaller than JPEG?

WebP is not always smaller than JPEG at the quality you need. A well-optimised JPEG can occasionally beat a poorly configured WebP export, so measure the actual output rather than assuming the newer format wins by default.

Can a thumbnail use a different format from its source?

A PHP thumbnail can use a different format from its source when the GD build supports both formats, though a transparent PNG converted to JPEG must be flattened onto a solid background first, because JPEG carries no alpha channel.

Does CSS resizing reduce the image download?

CSS resizing does not reduce the download because the browser still retrieves the selected image resource. Generate smaller files and describe them with srcset and sizes so the browser can choose an asset close to the space available.

Does GD rotate photos to match their EXIF orientation?

GD does not automatically apply EXIF orientation, so a phone photo that looks upright in a viewer can come out sideways in the thumbnail. Read the EXIF orientation and apply the required rotation or mirror operation before resizing.

Does cropping to a fixed size distort an image whose aspect ratio does not match?

Cropping a thumbnail preserves its proportions when the crop rectangle matches the target aspect ratio. Forcing a different ratio into a fixed width and height stretches the image. Calculate a crop rectangle that matches the target ratio first, centering it on the source image, then resize that rectangle to the final dimensions so proportions stay correct.

Save the Image Your Page Needs

Generate each thumbnail from the original, store the actual output dimensions, and serve the saved file. Verify that the encoder produced a non-empty image before adding it to the gallery.

Build the file pipeline first, then make the thumbnail grid pleasant. For saved overlays or watermarks, read composite images with PHP and GD. The HTML tutorials hub covers the browser side of gallery markup.

Sources

  1. [1]
  2. [2]
  3. [3]
  4. [4]
    PHP imagesx
    (php.net)
  5. [5]
    PHP imagesy
    (php.net)
  6. [6]
    PHP imagejpeg
    (php.net)
  7. [7]