Example results from resizing two large camera photos.
camera-photo-01.jpg
9504 × 6336 → 2400 × 1600
22.8 MB → 1.9 MB
camera-photo-02.jpg
9504 × 6336 → 2400 × 1600
18.4 MB → 1.5 MB
This PHP batch image resizer scans the directory where the script is installed and creates smaller web-ready copies of JPG, PNG, and WebP images.
Large photographs are resized by their longest side while keeping the original aspect ratio. A 9504 × 6336 camera image, for example, can be reduced to about 2400 × 1600 for normal web use.
The original photographs are never
overwritten. All resized copies are
written to a separate /web/
directory.
Modern cameras can produce images far larger than a normal browser needs. Uploading the full-resolution file means visitors may download many megabytes just to display a photo at a fraction of its original dimensions.
Creating smaller web copies can reduce page weight, improve gallery loading speed, reduce bandwidth use, and make large photo collections much easier to browse.
This tool is useful for photo galleries, camera uploads, personal websites, webmaster projects, image archives, and other directories containing large photographs that need smaller web copies.
It works especially well when you want a simple one-file PHP utility without installing a full image-management system.
Save the script as something like
resize-images.php inside
the directory containing the images
you want to process.
By default, resized copies are saved to
a new /web/ folder and the
originals are left untouched.
<?php
declare(strict_types=1);
/*
* PHP Batch Image Resizer
*
* Place this file inside the directory containing
* the images you want to resize.
*
* Resized copies are saved in /web/
* Originals are never overwritten.
*
* PHP 8.4 compatible.
*/
header('Content-Type: text/html; charset=UTF-8');
header(
'X-Robots-Tag: noindex, nofollow, noarchive, nosnippet',
true
);
/*
* --------------------------------------------------
* SETTINGS
* --------------------------------------------------
*/
$sourceDir = __DIR__;
$outputDir = __DIR__ . '/web';
/*
* Maximum size of the longest side.
*
* Example:
* 9504 x 6336
*
* becomes approximately:
* 2400 x 1600
*/
$maxDimension = 2400;
/*
* Output quality.
*
* 80-88 is normally a good range for photos.
*/
$imageQuality = 85;
/*
* Output format:
*
* original = keep JPG/PNG/WebP format
* jpeg = convert supported images to JPG
* webp = convert supported images to WebP
*/
$outputFormat = 'original';
/*
* Do not regenerate files that already exist.
*/
$skipExisting = true;
/*
* --------------------------------------------------
* SETUP
* --------------------------------------------------
*/
if (!is_dir($outputDir)) {
@mkdir($outputDir, 0755, true);
}
$allowedExtensions = [
'jpg',
'jpeg',
'png',
'webp'
];
$results = [];
$totalFound = 0;
$totalResized = 0;
$totalSkipped = 0;
$totalErrors = 0;
/*
* --------------------------------------------------
* HELPERS
* --------------------------------------------------
*/
function h(string $value): string
{
return htmlspecialchars(
$value,
ENT_QUOTES,
'UTF-8'
);
}
function formatBytes(int|float $bytes): string
{
$bytes = (float)$bytes;
if ($bytes >= 1073741824) {
return number_format(
$bytes / 1073741824,
2
) . ' GB';
}
if ($bytes >= 1048576) {
return number_format(
$bytes / 1048576,
2
) . ' MB';
}
if ($bytes >= 1024) {
return number_format(
$bytes / 1024,
1
) . ' KB';
}
return number_format($bytes, 0) . ' B';
}
function calculateSize(
int $width,
int $height,
int $maxDimension
): array {
if (
$width <= $maxDimension &&
$height <= $maxDimension
) {
return [$width, $height];
}
if ($width >= $height) {
$newWidth = $maxDimension;
$newHeight = (int)round(
$height *
($maxDimension / $width)
);
} else {
$newHeight = $maxDimension;
$newWidth = (int)round(
$width *
($maxDimension / $height)
);
}
return [$newWidth, $newHeight];
}
function outputExtension(
string $originalExtension,
string $outputFormat
): string {
if ($outputFormat === 'webp') {
return 'webp';
}
if ($outputFormat === 'jpeg') {
return 'jpg';
}
if ($originalExtension === 'jpeg') {
return 'jpg';
}
return $originalExtension;
}
/*
* --------------------------------------------------
* IMAGICK
* --------------------------------------------------
*/
function resizeWithImagick(
string $source,
string $destination,
int $maxDimension,
int $quality,
string $outputFormat
): bool {
try {
$image = new Imagick($source);
/*
* Correct camera orientation.
*/
if (method_exists($image, 'autoOrient')) {
$image->autoOrient();
}
$width = $image->getImageWidth();
$height = $image->getImageHeight();
[$newWidth, $newHeight] =
calculateSize(
$width,
$height,
$maxDimension
);
if (
$newWidth !== $width ||
$newHeight !== $height
) {
$image->resizeImage(
$newWidth,
$newHeight,
Imagick::FILTER_LANCZOS,
1
);
}
/*
* Remove unnecessary metadata/profiles.
*/
$image->stripImage();
$extension = strtolower(
pathinfo(
$destination,
PATHINFO_EXTENSION
)
);
if (
$extension === 'jpg' ||
$extension === 'jpeg'
) {
/*
* JPEG cannot store transparency.
*/
$image->setImageBackgroundColor('white');
if ($image->getImageAlphaChannel()) {
$image = $image->mergeImageLayers(
Imagick::LAYERMETHOD_FLATTEN
);
}
$image->setImageFormat('jpeg');
$image->setImageCompression(
Imagick::COMPRESSION_JPEG
);
$image->setImageCompressionQuality(
$quality
);
}
if ($extension === 'webp') {
$image->setImageFormat('webp');
$image->setImageCompressionQuality(
$quality
);
}
if ($extension === 'png') {
$image->setImageFormat('png');
$image->setImageCompressionQuality(
$quality
);
}
$success = $image->writeImage(
$destination
);
$image->clear();
$image->destroy();
return $success;
} catch (Throwable $e) {
return false;
}
}
/*
* --------------------------------------------------
* GD
* --------------------------------------------------
*/
function resizeWithGD(
string $source,
string $destination,
int $maxDimension,
int $quality
): bool {
$info = @getimagesize($source);
if ($info === false) {
return false;
}
$width = $info[0];
$height = $info[1];
$type = $info[2];
switch ($type) {
case IMAGETYPE_JPEG:
$sourceImage =
@imagecreatefromjpeg($source);
break;
case IMAGETYPE_PNG:
$sourceImage =
@imagecreatefrompng($source);
break;
case IMAGETYPE_WEBP:
if (
!function_exists(
'imagecreatefromwebp'
)
) {
return false;
}
$sourceImage =
@imagecreatefromwebp($source);
break;
default:
return false;
}
if (!$sourceImage) {
return false;
}
/*
* Correct common JPEG camera orientation.
*/
if (
$type === IMAGETYPE_JPEG &&
function_exists('exif_read_data')
) {
$exif = @exif_read_data($source);
if (!empty($exif['Orientation'])) {
switch (
(int)$exif['Orientation']
) {
case 3:
$sourceImage =
imagerotate(
$sourceImage,
180,
0
);
break;
case 6:
$sourceImage =
imagerotate(
$sourceImage,
-90,
0
);
break;
case 8:
$sourceImage =
imagerotate(
$sourceImage,
90,
0
);
break;
}
$width =
imagesx($sourceImage);
$height =
imagesy($sourceImage);
}
}
[$newWidth, $newHeight] =
calculateSize(
$width,
$height,
$maxDimension
);
$newImage =
imagecreatetruecolor(
$newWidth,
$newHeight
);
$extension = strtolower(
pathinfo(
$destination,
PATHINFO_EXTENSION
)
);
/*
* Preserve transparency when output
* supports it.
*/
if (
$extension === 'png' ||
$extension === 'webp'
) {
imagealphablending(
$newImage,
false
);
imagesavealpha(
$newImage,
true
);
$transparent =
imagecolorallocatealpha(
$newImage,
0,
0,
0,
127
);
imagefilledrectangle(
$newImage,
0,
0,
$newWidth,
$newHeight,
$transparent
);
} else {
/*
* White background for JPEG output.
*/
$white = imagecolorallocate(
$newImage,
255,
255,
255
);
imagefill(
$newImage,
0,
0,
$white
);
}
imagecopyresampled(
$newImage,
$sourceImage,
0,
0,
0,
0,
$newWidth,
$newHeight,
$width,
$height
);
$success = false;
if (
$extension === 'jpg' ||
$extension === 'jpeg'
) {
$success = imagejpeg(
$newImage,
$destination,
$quality
);
}
if ($extension === 'png') {
$pngCompression =
9 -
(int)round(
($quality / 100) * 9
);
$success = imagepng(
$newImage,
$destination,
$pngCompression
);
}
if (
$extension === 'webp' &&
function_exists('imagewebp')
) {
$success = imagewebp(
$newImage,
$destination,
$quality
);
}
imagedestroy($sourceImage);
imagedestroy($newImage);
return $success;
}
/*
* --------------------------------------------------
* PROCESS
* --------------------------------------------------
*/
if (
$_SERVER['REQUEST_METHOD'] === 'POST' &&
isset($_POST['resize_images'])
) {
$items = @scandir($sourceDir);
if ($items !== false) {
foreach ($items as $file) {
if (
$file === '.' ||
$file === '..'
) {
continue;
}
$sourceFile =
$sourceDir .
DIRECTORY_SEPARATOR .
$file;
if (!is_file($sourceFile)) {
continue;
}
$extension = strtolower(
pathinfo(
$file,
PATHINFO_EXTENSION
)
);
if (
!in_array(
$extension,
$allowedExtensions,
true
)
) {
continue;
}
$totalFound++;
$outputExtension =
outputExtension(
$extension,
$outputFormat
);
$baseName =
pathinfo(
$file,
PATHINFO_FILENAME
);
$destinationFile =
$outputDir .
DIRECTORY_SEPARATOR .
$baseName .
'.' .
$outputExtension;
$originalSize =
@filesize($sourceFile);
if (
$skipExisting &&
is_file($destinationFile)
) {
$results[] = [
'file' => $file,
'status' => 'Skipped',
'detail' => 'Already exists'
];
$totalSkipped++;
continue;
}
if (class_exists('Imagick')) {
$success =
resizeWithImagick(
$sourceFile,
$destinationFile,
$maxDimension,
$imageQuality,
$outputFormat
);
$engine = 'Imagick';
} elseif (extension_loaded('gd')) {
$success =
resizeWithGD(
$sourceFile,
$destinationFile,
$maxDimension,
$imageQuality
);
$engine = 'GD';
} else {
$success = false;
$engine = 'None';
}
if (
$success &&
is_file($destinationFile)
) {
$newSize =
@filesize(
$destinationFile
);
$results[] = [
'file' => $file,
'status' => 'Resized',
'detail' =>
formatBytes(
$originalSize ?: 0
) .
' → ' .
formatBytes(
$newSize ?: 0
) .
' (' .
$engine .
')'
];
$totalResized++;
} else {
$results[] = [
'file' => $file,
'status' => 'Error',
'detail' =>
'Could not resize with ' .
$engine
];
$totalErrors++;
}
}
}
}
/*
* --------------------------------------------------
* AVAILABLE ENGINE
* --------------------------------------------------
*/
if (class_exists('Imagick')) {
$availableEngine = 'Imagick';
} elseif (extension_loaded('gd')) {
$availableEngine = 'GD';
} else {
$availableEngine = 'None';
}
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta
name="viewport"
content="width=device-width,initial-scale=1"
>
<meta
name="robots"
content="noindex,nofollow,noarchive,nosnippet"
>
<title>PHP Batch Image Resizer</title>
<style>
body{
margin:0;
background:#111;
color:#eee;
font:16px/1.5 Arial,Helvetica,sans-serif;
}
.wrap{
max-width:850px;
margin:40px auto;
padding:24px;
}
h1{
margin:0 0 10px;
}
h2{
margin-top:30px;
}
.note{
color:#aaa;
}
.box{
margin:20px 0;
padding:16px;
background:#1b1b1b;
border:1px solid #333;
}
button{
padding:12px 18px;
font:inherit;
font-weight:bold;
cursor:pointer;
}
table{
width:100%;
border-collapse:collapse;
margin-top:15px;
}
th,
td{
padding:10px;
text-align:left;
border-bottom:1px solid #333;
}
th{
color:#aaa;
}
.good{
color:#8fe58f;
}
.skip{
color:#ddd27c;
}
.error{
color:#ff8a8a;
}
code{
color:#ddd;
}
</style>
</head>
<body>
<div class="wrap">
<h1>PHP Batch Image Resizer</h1>
<p class="note">
Creates web-sized copies of large images
without changing the originals.
</p>
<div class="box">
<strong>Settings</strong>
<p>
Maximum dimension:
<strong>
<?php echo h((string)$maxDimension); ?> px
</strong>
</p>
<p>
Image quality:
<strong>
<?php echo h((string)$imageQuality); ?>%
</strong>
</p>
<p>
Output format:
<strong>
<?php echo h($outputFormat); ?>
</strong>
</p>
<p>
Output directory:
<code>/web/</code>
</p>
<p>
Image engine:
<strong>
<?php echo h($availableEngine); ?>
</strong>
</p>
</div>
<?php if ($availableEngine === 'None'): ?>
<div class="box">
<strong>
No image library available.
</strong>
<p>
This server needs either the PHP Imagick
extension or PHP GD extension.
</p>
</div>
<?php else: ?>
<form method="post">
<input
type="hidden"
name="resize_images"
value="1"
>
<button type="submit">
Resize Images
</button>
</form>
<?php endif; ?>
<?php if (!empty($results)): ?>
<h2>Results</h2>
<div class="box">
<p>
Found:
<strong><?php echo $totalFound; ?></strong>
—
Resized:
<strong><?php echo $totalResized; ?></strong>
—
Skipped:
<strong><?php echo $totalSkipped; ?></strong>
—
Errors:
<strong><?php echo $totalErrors; ?></strong>
</p>
<table>
<thead>
<tr>
<th>Image</th>
<th>Status</th>
<th>Size</th>
</tr>
</thead>
<tbody>
<?php foreach ($results as $result): ?>
<tr>
<td>
<?php echo h($result['file']); ?>
</td>
<td class="<?php
echo strtolower($result['status']) === 'resized'
? 'good'
: (
strtolower($result['status']) === 'error'
? 'error'
: 'skip'
);
?>">
<?php echo h($result['status']); ?>
</td>
<td>
<?php echo h($result['detail']); ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
<div class="box">
<strong>Important</strong>
<p>
Original images are never overwritten.
</p>
<p>
Resized copies are placed in:
</p>
<code>/web/</code>
<p>
Remove or protect this script after use so
random visitors cannot start a resize job.
</p>
</div>
</div>
</body>
</html>
Maximum long side: 2400 pixels
Image quality: 85%
Output: same image format
Output directory:
/web/
You can change these values near the top of the script before running it.
By default, the script keeps each image's original format.
To create WebP copies instead, change:
$outputFormat = 'original';
to:
$outputFormat = 'webp';
The script automatically uses Imagick when the PHP Imagick extension is available.
If Imagick is not installed, it falls back to PHP GD.
Imagick is generally the better choice for very large camera images because high-resolution photographs can require substantial memory when processed with GD.
The resize script already sends a
noindex header. You can also
block its URL in robots.txt.
User-agent: * Disallow: /resize-images.php
If the script is installed inside a subdirectory, use its actual path instead.
This is not a full image manager. It does not recursively scan every directory on the server, edit the original photographs, or automatically replace images used by an existing gallery.
Extremely large images can also exceed a hosting account's PHP memory limit, particularly when GD is used instead of Imagick.
Do not leave the working resize script publicly accessible after you are finished using it.
Because resizing large images consumes server CPU and memory, remove the script, rename it, password-protect it, or otherwise restrict access once the batch job is complete.
The public VibeScriptz page is only a demonstration and code example. It does not accept uploads or process visitor images.
The actual resizing happens entirely on the server where the copied PHP script is installed.