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>
194 lines
5.4 KiB
PHP
194 lines
5.4 KiB
PHP
<?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)
|
|
]);
|