100, 'max_width' => 600, 'min_height' => 100, 'max_height' => 600, 'square' => false, 'allowed_types' => ['image/png', 'image/jpeg', 'image/webp'], 'max_size_kb' => 2048, // 2MB 'transparent' => false ]; private $finalImage = null; public function __construct($imageName) { if (isset($_FILES[$imageName]) && $_FILES[$imageName]['error'] === UPLOAD_ERR_OK) { $this->postedFile = $_FILES[$imageName]; $this->imageTmpPath = $this->postedFile['tmp_name']; $this->imageInfo = getimagesize($this->imageTmpPath); } elseif (isset($_POST['image_base64'])) { $base64 = $_POST['image_base64']; if (preg_match('/^data:(image\/\w+);base64,/', $base64, $matches)) { $mimeType = $matches[1]; $base64 = substr($base64, strpos($base64, ',') + 1); } else { throw new Exception('Invalid image data.'); } $imageData = base64_decode($base64); if ($imageData === false) { throw new Exception('Invalid base64 image data.'); } # Create image directly from string (no file) $srcImage = imagecreatefromstring($imageData); if (!$srcImage) { throw new Exception('Failed to create image from string.'); } # Now you can get dimensions directly $width = imagesx($srcImage); $height = imagesy($srcImage); # Store $srcImage in a class property, continue processing in-memory $this->imageResource = $srcImage; $this->imageInfo = [ 'mime' => $mimeType, 'width' => $width, 'height' => $height, 'size' => strlen($imageData) ]; } } public function validateAndProcess() { if (!$this->postedFile) { return true; } $width = $this->imageInfo[0]; $height = $this->imageInfo[1]; $mime = $this->imageInfo['mime']; $fileSizeKB = filesize($this->imageTmpPath) / 1024; if (!in_array($mime, $this->imageRestrictions['allowed_types'])) { throw new Exception("Invalid image type: $mime"); } if ($fileSizeKB > $this->imageRestrictions['max_size_kb']) { throw new Exception("Image exceeds max file size."); } # Resize to fit within min/max bounds $resizedImage = $this->resizeToFitRestrictions($mime, $width, $height); # Optionally square it if ($this->imageRestrictions['square']) { $resizedImage = $this->makeImageSquare($resizedImage, $mime); } ob_start(); switch ($mime) { case 'image/jpeg': imagejpeg($resizedImage); break; case 'image/png': imagepng($resizedImage); break; case 'image/webp': imagewebp($resizedImage); break; } $this->finalImage = ob_get_clean(); imagedestroy($resizedImage); return true; } private function mimeSupportsTransparency($mime) { return in_array($mime, ['image/png', 'image/webp']); } private function setTransparentCanvas($image, $width, $height) { // Enable alpha blending and preserve alpha channel imagealphablending($image, false); imagesavealpha($image, true); // Fill the image with a fully transparent color $transparent = imagecolorallocatealpha($image, 0, 0, 0, 127); imagefilledrectangle($image, 0, 0, $width, $height, $transparent); return $image; } private function resizeToFitRestrictions($mime, $width, $height) { $minW = $this->imageRestrictions['min_width']; $maxW = $this->imageRestrictions['max_width']; $minH = $this->imageRestrictions['min_height']; $maxH = $this->imageRestrictions['max_height']; $srcImage = match ($mime) { 'image/jpeg' => imagecreatefromjpeg($this->imageTmpPath), 'image/png' => imagecreatefrompng($this->imageTmpPath), 'image/webp' => imagecreatefromwebp($this->imageTmpPath), default => throw new Exception("Unsupported image type.") }; # Determine new size $newWidth = $width; $newHeight = $height; if ($width < $minW || $width > $maxW || $height < $minH || $height > $maxH) { # Calculate scale factor based on limits $widthScale = ($width < $minW) ? $minW / $width : ($width > $maxW ? $maxW / $width : 1); $heightScale = ($height < $minH) ? $minH / $height : ($height > $maxH ? $maxH / $height : 1); # Use the smallest scale to ensure both dimensions fit $scale = min($widthScale, $heightScale); $newWidth = round($width * $scale); $newHeight = round($height * $scale); } $resizedImage = imagecreatetruecolor($newWidth, $newHeight); # keep transparent if ($this->mimeSupportsTransparency($mime)) { $this->setTransparentCanvas($resizedImage, $newWidth, $newHeight); } imagecopyresampled($resizedImage, $srcImage, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height); imagedestroy($srcImage); return $resizedImage; } private function makeImageSquare($srcImage, $mime) { $width = imagesx($srcImage); $height = imagesy($srcImage); $size = min($width, $height); $x = ($width - $size) / 2; $y = ($height - $size) / 2; $squareImage = imagecreatetruecolor($size, $size); # keep transparent if ($this->mimeSupportsTransparency($mime)) { $this->setTransparentCanvas($squareImage, $size, $size); } imagecopyresampled($squareImage, $srcImage, 0, 0, $x, $y, $size, $size, $size, $size); imagedestroy($srcImage); return $squareImage; } public function returnBase64image() { if ($this->finalImage) { return base64_encode($this->finalImage); } else { return false; } } }