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 ]; } } ?>