Initial commit: Site Builder with PHP API backend
Visual drag-and-drop website builder using GrapesJS with: - Multi-page editor with live preview - File-based asset storage via PHP API (no localStorage base64) - Template library, Docker support, and Playwright test suite Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
193
api/image-resize.php
Normal file
193
api/image-resize.php
Normal file
@@ -0,0 +1,193 @@
|
||||
<?php
|
||||
/**
|
||||
* Image Resize/Crop API
|
||||
*
|
||||
* Usage:
|
||||
* POST /api/image-resize.php
|
||||
* Parameters:
|
||||
* - image: uploaded file (multipart) OR url (string)
|
||||
* - width: target width (int)
|
||||
* - height: target height (int)
|
||||
* - mode: 'resize' | 'crop' | 'fit' (default: resize)
|
||||
* - quality: 1-100 (default: 85)
|
||||
* - format: 'jpg' | 'png' | 'webp' (default: auto)
|
||||
*
|
||||
* Returns: JSON { success: true, url: "path/to/resized.jpg", width: N, height: N }
|
||||
*/
|
||||
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: POST, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
http_response_code(204);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405);
|
||||
echo json_encode(['error' => 'Method not allowed']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Configuration
|
||||
$uploadDir = __DIR__ . '/../uploads/';
|
||||
$outputDir = __DIR__ . '/../uploads/resized/';
|
||||
$maxFileSize = 10 * 1024 * 1024; // 10MB
|
||||
|
||||
// Create directories
|
||||
if (!is_dir($uploadDir)) mkdir($uploadDir, 0755, true);
|
||||
if (!is_dir($outputDir)) mkdir($outputDir, 0755, true);
|
||||
|
||||
// Get parameters
|
||||
$width = intval($_POST['width'] ?? 0);
|
||||
$height = intval($_POST['height'] ?? 0);
|
||||
$mode = $_POST['mode'] ?? 'resize';
|
||||
$quality = intval($_POST['quality'] ?? 85);
|
||||
$format = $_POST['format'] ?? 'auto';
|
||||
|
||||
if ($width <= 0 && $height <= 0) {
|
||||
echo json_encode(['error' => 'Width or height required']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$quality = max(1, min(100, $quality));
|
||||
|
||||
// Get source image
|
||||
$sourcePath = null;
|
||||
$cleanup = false;
|
||||
|
||||
if (isset($_FILES['image']) && $_FILES['image']['error'] === UPLOAD_ERR_OK) {
|
||||
if ($_FILES['image']['size'] > $maxFileSize) {
|
||||
echo json_encode(['error' => 'File too large (max 10MB)']);
|
||||
exit;
|
||||
}
|
||||
$sourcePath = $_FILES['image']['tmp_name'];
|
||||
} elseif (!empty($_POST['url'])) {
|
||||
$url = filter_var($_POST['url'], FILTER_VALIDATE_URL);
|
||||
if (!$url) {
|
||||
echo json_encode(['error' => 'Invalid URL']);
|
||||
exit;
|
||||
}
|
||||
$tempFile = tempnam(sys_get_temp_dir(), 'img_');
|
||||
$content = @file_get_contents($url);
|
||||
if ($content === false) {
|
||||
echo json_encode(['error' => 'Could not download image']);
|
||||
exit;
|
||||
}
|
||||
file_put_contents($tempFile, $content);
|
||||
$sourcePath = $tempFile;
|
||||
$cleanup = true;
|
||||
} else {
|
||||
echo json_encode(['error' => 'No image provided']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Detect image type
|
||||
$info = @getimagesize($sourcePath);
|
||||
if (!$info) {
|
||||
if ($cleanup) unlink($sourcePath);
|
||||
echo json_encode(['error' => 'Invalid image file']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$srcWidth = $info[0];
|
||||
$srcHeight = $info[1];
|
||||
$mime = $info['mime'];
|
||||
|
||||
// Create source image resource
|
||||
switch ($mime) {
|
||||
case 'image/jpeg': $srcImg = imagecreatefromjpeg($sourcePath); break;
|
||||
case 'image/png': $srcImg = imagecreatefrompng($sourcePath); break;
|
||||
case 'image/gif': $srcImg = imagecreatefromgif($sourcePath); break;
|
||||
case 'image/webp': $srcImg = imagecreatefromwebp($sourcePath); break;
|
||||
default:
|
||||
if ($cleanup) unlink($sourcePath);
|
||||
echo json_encode(['error' => 'Unsupported image type: ' . $mime]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($cleanup) unlink($sourcePath);
|
||||
|
||||
// Calculate dimensions
|
||||
$dstWidth = $width;
|
||||
$dstHeight = $height;
|
||||
$srcX = 0;
|
||||
$srcY = 0;
|
||||
$cropWidth = $srcWidth;
|
||||
$cropHeight = $srcHeight;
|
||||
|
||||
if ($mode === 'resize') {
|
||||
if ($dstWidth <= 0) $dstWidth = intval($srcWidth * ($dstHeight / $srcHeight));
|
||||
if ($dstHeight <= 0) $dstHeight = intval($srcHeight * ($dstWidth / $srcWidth));
|
||||
} elseif ($mode === 'fit') {
|
||||
if ($dstWidth <= 0) $dstWidth = $srcWidth;
|
||||
if ($dstHeight <= 0) $dstHeight = $srcHeight;
|
||||
$ratio = min($dstWidth / $srcWidth, $dstHeight / $srcHeight);
|
||||
$dstWidth = intval($srcWidth * $ratio);
|
||||
$dstHeight = intval($srcHeight * $ratio);
|
||||
} elseif ($mode === 'crop') {
|
||||
if ($dstWidth <= 0) $dstWidth = $dstHeight;
|
||||
if ($dstHeight <= 0) $dstHeight = $dstWidth;
|
||||
$ratio = max($dstWidth / $srcWidth, $dstHeight / $srcHeight);
|
||||
$cropWidth = intval($dstWidth / $ratio);
|
||||
$cropHeight = intval($dstHeight / $ratio);
|
||||
$srcX = intval(($srcWidth - $cropWidth) / 2);
|
||||
$srcY = intval(($srcHeight - $cropHeight) / 2);
|
||||
}
|
||||
|
||||
// Create destination image
|
||||
$dstImg = imagecreatetruecolor($dstWidth, $dstHeight);
|
||||
|
||||
// Preserve transparency for PNG
|
||||
if ($mime === 'image/png' || $format === 'png') {
|
||||
imagealphablending($dstImg, false);
|
||||
imagesavealpha($dstImg, true);
|
||||
}
|
||||
|
||||
// Resample
|
||||
imagecopyresampled($dstImg, $srcImg, 0, 0, $srcX, $srcY, $dstWidth, $dstHeight, $cropWidth, $cropHeight);
|
||||
|
||||
// Determine output format
|
||||
if ($format === 'auto') {
|
||||
$format = match($mime) {
|
||||
'image/png' => 'png',
|
||||
'image/gif' => 'png',
|
||||
'image/webp' => 'webp',
|
||||
default => 'jpg'
|
||||
};
|
||||
}
|
||||
|
||||
// Generate output filename
|
||||
$filename = 'img_' . uniqid() . '_' . $dstWidth . 'x' . $dstHeight . '.' . $format;
|
||||
$outputPath = $outputDir . $filename;
|
||||
|
||||
// Save
|
||||
switch ($format) {
|
||||
case 'jpg':
|
||||
case 'jpeg':
|
||||
imagejpeg($dstImg, $outputPath, $quality);
|
||||
break;
|
||||
case 'png':
|
||||
imagepng($dstImg, $outputPath, intval(9 - ($quality / 100 * 9)));
|
||||
break;
|
||||
case 'webp':
|
||||
imagewebp($dstImg, $outputPath, $quality);
|
||||
break;
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
imagedestroy($srcImg);
|
||||
imagedestroy($dstImg);
|
||||
|
||||
// Return result
|
||||
$relPath = 'uploads/resized/' . $filename;
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'url' => $relPath,
|
||||
'width' => $dstWidth,
|
||||
'height' => $dstHeight,
|
||||
'format' => $format,
|
||||
'size' => filesize($outputPath)
|
||||
]);
|
||||
Reference in New Issue
Block a user