萬盛學電腦網

您现在的位置: 萬盛學電腦網 >> 程式設計 >  >> 網路程式設計 >> PHP 程式設計

PHP 圖片無損裁切縮放:保持比例、智慧裁切、支援 JPEG/PNG/GIF/WebP

前言

網站常需將使用者上傳的圖片產生多種尺寸(縮圖、列表圖、詳情圖)。直接縮放會變形,單純裁切可能切掉主體。本文提供一個智慧裁切縮放函式:依目標長寬比,自動決定「先縮放再置中裁切」或「先裁切再縮放」,確保主體完整、無變形、無留白。

核心演算法邏輯

  1. 計算原圖與目標長寬比:$srcRatio = $srcW / $srcH, $dstRatio = $dstW / $dstH
  2. 原圖較寬($srcRatio > $dstRatio):以高度為基準縮放,寬度會超出 → 置中裁切左右多餘部分
  3. 原圖較高($srcRatio < $dstRatio):以寬度為基準縮放,高度會超出 → 置中裁切上下多餘部分
  4. 比例相同:直接縮放

完整函式實作

<?php
/**
 * 智慧裁切縮放:保持比例、置中裁切、無變形
 *
 * @param string $srcPath 來源圖片路徑
 * @param string $dstPath 目標儲存路徑
 * @param int    $dstWidth  目標寬度
 * @param int    $dstHeight 目標高度
 * @param int    $quality   JPEG/WebP 品質 0-100
 * @return bool 成功與否
 */
function smart_resize_crop(string $srcPath, string $dstPath, int $dstWidth, int $dstHeight, int $quality = 90): bool
{
    if (!file_exists($srcPath)) {
        trigger_error("來源檔案不存在: $srcPath", E_USER_WARNING);
        return false;
    }
    if ($dstWidth <= 0 || $dstHeight <= 0) {
        trigger_error('目標寬高必須大於 0', E_USER_WARNING);
        return false;
    }

    // 1. 偵測圖片類型並建立資源
    $type = exif_imagetype($srcPath);
    $supported = [IMAGETYPE_JPEG, IMAGETYPE_PNG, IMAGETYPE_GIF, IMAGETYPE_WEBP];
    if (!in_array($type, $supported, true)) {
        trigger_error('不支援的圖片格式,僅支援 JPEG/PNG/GIF/WebP', E_USER_WARNING);
        return false;
    }

    $createFunc = [
        IMAGETYPE_JPEG  => 'imagecreatefromjpeg',
        IMAGETYPE_PNG   => 'imagecreatefrompng',
        IMAGETYPE_GIF   => 'imagecreatefromgif',
        IMAGETYPE_WEBP  => 'imagecreatefromwebp',
    ];
    $srcImg = $createFunc[$type]($srcPath);
    if (!$srcImg) {
        trigger_error('建立圖片資源失敗', E_USER_WARNING);
        return false;
    }

    // 2. 取得原始尺寸
    $srcW = imagesx($srcImg);
    $srcH = imagesy($srcImg);

    // 3. 計算縮放比例與中間畫布尺寸
    $srcRatio = $srcW / $srcH;
    $dstRatio = $dstWidth / $dstHeight;

    if ($srcRatio > $dstRatio) {
        // 原圖較寬 → 以高度為基準縮放
        $interW = (int)round($dstHeight * $srcRatio);
        $interH = $dstHeight;
    } else {
        // 原圖較高或相等 → 以寬度為基準縮放
        $interW = $dstWidth;
        $interH = (int)round($dstWidth / $srcRatio);
    }

    // 4. 建立中間畫布並縮放
    $interImg = imagecreatetruecolor($interW, $interH);
    // 保留透明度(PNG/GIF/WebP)
    if ($type === IMAGETYPE_PNG || $type === IMAGETYPE_GIF || $type === IMAGETYPE_WEBP) {
        imagealphablending($interImg, false);
        imagesavealpha($interImg, true);
        $transparent = imagecolorallocatealpha($interImg, 0, 0, 0, 127);
        imagefill($interImg, 0, 0, $transparent);
    }
    imagecopyresampled($interImg, $srcImg, 0, 0, 0, 0, $interW, $interH, $srcW, $srcH);

    // 5. 置中裁切至目標尺寸
    $dstImg = imagecreatetruecolor($dstWidth, $dstHeight);
    if ($type === IMAGETYPE_PNG || $type === IMAGETYPE_GIF || $type === IMAGETYPE_WEBP) {
        imagealphablending($dstImg, false);
        imagesavealpha($dstImg, true);
        $transparent = imagecolorallocatealpha($dstImg, 0, 0, 0, 127);
        imagefill($dstImg, 0, 0, $transparent);
    }

    $srcX = ($interW - $dstWidth) / 2;
    $srcY = ($interH - $dstHeight) / 2;
    imagecopy($dstImg, $interImg, 0, 0, $srcX, $srcY, $dstWidth, $dstHeight);

    // 6. 輸出儲存
    $outputFunc = [
        IMAGETYPE_JPEG  => fn($img, $path) => imagejpeg($img, $path, $quality),
        IMAGETYPE_PNG   => fn($img, $path) => imagepng($img, $path, (int)round($quality / 10)), // PNG 壓縮等級 0-9
        IMAGETYPE_GIF   => fn($img, $path) => imagegif($img, $path),
        IMAGETYPE_WEBP  => fn($img, $path) => imagewebp($img, $path, $quality),
    ];
    $success = $outputFunc[$type]($dstImg, $dstPath);

    // 7. 釋放記憶體
    imagedestroy($srcImg);
    imagedestroy($interImg);
    imagedestroy($dstImg);

    return $success;
}

使用範例

<?php
require_once 'smart_resize_crop.php';

// 產生 400x300 縮圖
smart_resize_crop('uploads/photo.jpg', 'thumbs/photo_400x300.jpg', 400, 300, 85);

// 批次處理上傳目錄
$targetSizes = [
    'thumb'  => [150, 150],
    'medium' => [400, 300],
    'large'  => [800, 600],
];
foreach (glob('uploads/*.{jpg,jpeg,png,gif,webp}', GLOB_BRACE) as $file) {
    $baseName = pathinfo($file, PATHINFO_FILENAME);
    foreach ($targetSizes as $prefix => [$w, $h]) {
        $out = "thumbs/{$baseName}_{$prefix}.jpg";
        smart_resize_crop($file, $out, $w, $h, 85);
    }
}

進階:支援 WebP 輸出(現代瀏覽器首選)

// 修改輸出函式,統一輸出 WebP
$outputFunc = [
    IMAGETYPE_JPEG  => fn($img, $path) => imagewebp($img, $path, $quality),
    IMAGETYPE_PNG   => fn($img, $path) => imagewebp($img, $path, $quality),
    IMAGETYPE_GIF   => fn($img, $path) => imagewebp($img, $path, $quality),
    IMAGETYPE_WEBP  => fn($img, $path) => imagewebp($img, $path, $quality),
];
// 並將 $dstPath 副檔名改為 .webp

效能優化建議

  • 記憶體限制:大圖處理需調大 memory_limit(如 256M)。
  • 非同步處理:上傳後丟入佇列(Redis/RabbitMQ),由 Worker 背景處理縮圖,避免阻塞回應。
  • 快取:同尺寸圖片產生後快取至 CDN 或本地,避免重複運算。
  • 使用 ImageMagick (imagick 擴展):效能更高、格式支援更全、指令列 convert 更靈活。

結語

智慧裁切縮放的核心在於比例比較 → 基準縮放 → 置中裁切三步驟。封裝為可重複使用的函式,並妥善處理透明度、記憶體釋放、多格式輸出,即可滿足多數 Web 專案的圖片處理需求。若流量大、圖片多,建議升級至 ImageMagick 或雲端圖片服務。

copyright © 萬盛學電腦網 all rights reserved