<?php
class ImageProcessor {
private $maxWidth;
private $maxHeight;
private $quality;
private $thumbnailSize;
private $supportedFormats;
public function __construct($config = null) {
// Default configuration
$defaultConfig = [
'max_width' => 1000,
'max_height' => 1000,
'webp_quality' => 80,
'thumbnail_size' => 300
];
$config = $config ? array_merge($defaultConfig, $config) : $defaultConfig;
$this->maxWidth = $config['max_width'];
$this->maxHeight = $config['max_height'];
$this->quality = $config['webp_quality'];
$this->thumbnailSize = $config['thumbnail_size'];
// Supported image formats
$this->supportedFormats = [
'image/jpeg' => 'jpeg',
'image/jpg' => 'jpeg',
'image/png' => 'png',
'image/gif' => 'gif',
'image/bmp' => 'bmp',
'image/x-ms-bmp' => 'bmp',
'image/tiff' => 'tiff',
'image/tif' => 'tiff',
'image/webp' => 'webp'
];
}
/**
* Validate if the uploaded file is a supported image format
*/
public function validateImage($filePath) {
if (!file_exists($filePath)) {
throw new Exception("File does not exist: " . $filePath);
}
// Check file size
$fileSize = filesize($filePath);
if ($fileSize === false || $fileSize === 0) {
throw new Exception("Invalid file or empty file");
}
// Get MIME type
$mimeType = mime_content_type($filePath);
if (!$mimeType || !array_key_exists($mimeType, $this->supportedFormats)) {
throw new Exception("Unsupported image format: " . $mimeType);
}
// Additional validation using getimagesize
$imageInfo = getimagesize($filePath);
if ($imageInfo === false) {
throw new Exception("Invalid image file");
}
return [
'mime_type' => $mimeType,
'width' => $imageInfo[0],
'height' => $imageInfo[1],
'file_size' => $fileSize,
'format' => $this->supportedFormats[$mimeType]
];
}
/**
* Convert image to WebP format with resizing
*/
public function convertToWebP($inputPath, $outputPath) {
$imageInfo = $this->validateImage($inputPath);
// Create image resource from input file
$sourceImage = $this->createImageFromFile($inputPath, $imageInfo['format']);
if (!$sourceImage) {
throw new Exception("Failed to create image resource from: " . $inputPath);
}
// Calculate new dimensions while maintaining aspect ratio
$newDimensions = $this->calculateResizeDimensions(
$imageInfo['width'],
$imageInfo['height'],
$this->maxWidth,
$this->maxHeight
);
// Create resized image
$resizedImage = $this->resizeImage($sourceImage, $newDimensions);
// Ensure output directory exists
$outputDir = dirname($outputPath);
if (!is_dir($outputDir)) {
mkdir($outputDir, 0755, true);
}
// Save as WebP
$success = imagewebp($resizedImage, $outputPath, $this->quality);
// Clean up memory
imagedestroy($sourceImage);
imagedestroy($resizedImage);
if (!$success) {
throw new Exception("Failed to save WebP image: " . $outputPath);
}
return [
'original_width' => $imageInfo['width'],
'original_height' => $imageInfo['height'],
'new_width' => $newDimensions['width'],
'new_height' => $newDimensions['height'],
'file_size' => filesize($outputPath)
];
}
/**
* Generate thumbnail for gallery previews
*/
public function generateThumbnail($inputPath, $outputPath, $size = null) {
$thumbnailSize = $size ?: $this->thumbnailSize;
$imageInfo = $this->validateImage($inputPath);
// Create image resource from input file
$sourceImage = $this->createImageFromFile($inputPath, $imageInfo['format']);
if (!$sourceImage) {
throw new Exception("Failed to create image resource for thumbnail: " . $inputPath);
}
// Calculate thumbnail dimensions (square crop)
$cropDimensions = $this->calculateCropDimensions(
$imageInfo['width'],
$imageInfo['height'],
$thumbnailSize
);
// Create thumbnail
$thumbnail = imagecreatetruecolor($thumbnailSize, $thumbnailSize);
// Preserve transparency for PNG and GIF
if ($imageInfo['format'] === 'png' || $imageInfo['format'] === 'gif') {
imagealphablending($thumbnail, false);
imagesavealpha($thumbnail, true);
$transparent = imagecolorallocatealpha($thumbnail, 255, 255, 255, 127);
imagefill($thumbnail, 0, 0, $transparent);
}
// Copy and resize image to create thumbnail
imagecopyresampled(
$thumbnail, $sourceImage,
0, 0, // Destination x, y
$cropDimensions['src_x'], $cropDimensions['src_y'], // Source x, y
$thumbnailSize, $thumbnailSize, // Destination width, height
$cropDimensions['src_width'], $cropDimensions['src_height'] // Source width, height
);
// Ensure output directory exists
$outputDir = dirname($outputPath);
if (!is_dir($outputDir)) {
mkdir($outputDir, 0755, true);
}
// Save thumbnail as WebP
$success = imagewebp($thumbnail, $outputPath, $this->quality);
// Clean up memory
imagedestroy($sourceImage);
imagedestroy($thumbnail);
if (!$success) {
throw new Exception("Failed to save thumbnail: " . $outputPath);
}
return [
'thumbnail_size' => $thumbnailSize,
'file_size' => filesize($outputPath)
];
}
/**
* Get image dimensions without loading the full image
*/
public function getImageDimensions($filePath) {
$imageInfo = getimagesize($filePath);
if ($imageInfo === false) {
throw new Exception("Cannot get image dimensions: " . $filePath);
}
return [
'width' => $imageInfo[0],
'height' => $imageInfo[1]
];
}
/**
* Create image resource from file based on format
*/
private function createImageFromFile($filePath, $format) {
switch ($format) {
case 'jpeg':
return imagecreatefromjpeg($filePath);
case 'png':
return imagecreatefrompng($filePath);
case 'gif':
return imagecreatefromgif($filePath);
case 'bmp':
return imagecreatefrombmp($filePath);
case 'webp':
return imagecreatefromwebp($filePath);
default:
throw new Exception("Unsupported image format for processing: " . $format);
}
}
/**
* Resize image while maintaining aspect ratio
*/
private function resizeImage($sourceImage, $dimensions) {
$resizedImage = imagecreatetruecolor($dimensions['width'], $dimensions['height']);
// Preserve transparency for PNG and GIF
imagealphablending($resizedImage, false);
imagesavealpha($resizedImage, true);
$transparent = imagecolorallocatealpha($resizedImage, 255, 255, 255, 127);
imagefill($resizedImage, 0, 0, $transparent);
// Copy and resize
imagecopyresampled(
$resizedImage, $sourceImage,
0, 0, 0, 0,
$dimensions['width'], $dimensions['height'],
imagesx($sourceImage), imagesy($sourceImage)
);
return $resizedImage;
}
/**
* Calculate new dimensions while maintaining aspect ratio
*/
private function calculateResizeDimensions($originalWidth, $originalHeight, $maxWidth, $maxHeight) {
// If image is already smaller than max dimensions, keep original size
if ($originalWidth <= $maxWidth && $originalHeight <= $maxHeight) {
return [
'width' => $originalWidth,
'height' => $originalHeight
];
}
// Calculate aspect ratio
$aspectRatio = $originalWidth / $originalHeight;
// Calculate new dimensions
if ($originalWidth > $originalHeight) {
// Landscape orientation
$newWidth = min($maxWidth, $originalWidth);
$newHeight = round($newWidth / $aspectRatio);
// Check if height exceeds max height
if ($newHeight > $maxHeight) {
$newHeight = $maxHeight;
$newWidth = round($newHeight * $aspectRatio);
}
} else {
// Portrait or square orientation
$newHeight = min($maxHeight, $originalHeight);
$newWidth = round($newHeight * $aspectRatio);
// Check if width exceeds max width
if ($newWidth > $maxWidth) {
$newWidth = $maxWidth;
$newHeight = round($newWidth / $aspectRatio);
}
}
return [
'width' => (int)$newWidth,
'height' => (int)$newHeight
];
}
/**
* Calculate crop dimensions for square thumbnail
*/
private function calculateCropDimensions($originalWidth, $originalHeight, $thumbnailSize) {
// Determine the smaller dimension to create a square crop
$cropSize = min($originalWidth, $originalHeight);
// Calculate starting position to center the crop
$srcX = ($originalWidth - $cropSize) / 2;
$srcY = ($originalHeight - $cropSize) / 2;
return [
'src_x' => (int)$srcX,
'src_y' => (int)$srcY,
'src_width' => $cropSize,
'src_height' => $cropSize
];
}
}
?>