ImageProcessor.php

9.92 KB
29/07/2025 16:31
PHP
ImageProcessor.php
<?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
        ];
    }
}

?>