首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 縮略圖生成四大核心方法深度解析與實戰代碼

     編輯:PHP 相關知識

前言

PHP 縮略圖生成看似簡單,實則涉及多種業務模式(等比縮放、填滿裁剪、固定畫布居中、添加水印等)。本文系統梳理四大核心方法,從底層 GD 函數原理到封裝類設計,提供可直接落地的代碼與選型指南。

方法一:基礎等比縮放(最常用)

核心邏輯:min($max_w/$src_w, $max_h/$src_h) 計算縮放比,確保圖片完整放入目標框內。

<?php
function resize_fit($src, $dst, $max_w, $max_h, $quality = 90) {
    $info = getimagesize($src);
    if (!$info) return false;
    [$src_w, $src_h, $type] = $info;
    
    $ratio = min($max_w / $src_w, $max_h / $src_h);
    $dst_w = (int)round($src_w * $ratio);
    $dst_h = (int)round($src_h * $ratio);
    
    $src_img = load_image($src, $type);
    $dst_img = imagecreatetruecolor($dst_w, $dst_h);
    copy_transparency($src_img, $dst_img, $type); // PNG/GIF 透明處理
    imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $dst_w, $dst_h, $src_w, $src_h);
    save_image($dst_img, $dst, $type, $quality);
    imagedestroy($src_img);
    imagedestroy($dst_img);
    return true;
}

方法二:固定畫布居中(不裁剪、不變形、留白填充)

適用:商品主圖統一尺寸、相冊網格、需要固定長寬比的場景。

function resize_canvas($src, $dst, $canvas_w, $canvas_h, $bg_color = '#FFFFFF', $quality = 90) {
    $info = getimagesize($src);
    if (!$info) return false;
    [$src_w, $src_h, $type] = $info;
    
    $src_ratio = $src_w / $src_h;
    $dst_ratio = $canvas_w / $canvas_h;
    
    if ($dst_ratio > $src_ratio) {
        // 目標更寬:以高為準
        $new_h = $canvas_h;
        $new_w = (int)round($canvas_h * $src_ratio);
        $x = (int)(($canvas_w - $new_w) / 2);
        $y = 0;
    } else {
        // 目標更高:以寬為準
        $new_w = $canvas_w;
        $new_h = (int)round($canvas_w / $src_ratio);
        $x = 0;
        $y = (int)(($canvas_h - $new_h) / 2);
    }
    
    $src_img = load_image($src, $type);
    $dst_img = imagecreatetruecolor($canvas_w, $canvas_h);
    fill_bg($dst_img, $bg_color);
    copy_transparency($src_img, $dst_img, $type);
    imagecopyresampled($dst_img, $src_img, $x, $y, 0, 0, $new_w, $new_h, $src_w, $src_h);
    save_image($dst_img, $dst, $type, $quality);
    imagedestroy($src_img);
    imagedestroy($dst_img);
    return true;
}

方法三:填滿裁剪(Cover 模式,無留白、不變形、居中裁剪)

適用:Banner、封面、頭像方框、背景圖。

function resize_cover($src, $dst, $target_w, $target_h, $quality = 90) {
    $info = getimagesize($src);
    if (!$info) return false;
    [$src_w, $src_h, $type] = $info;
    
    $src_ratio = $src_w / $src_h;
    $dst_ratio = $target_w / $target_h;
    
    if ($dst_ratio > $src_ratio) {
        // 目標更寬:源圖以高為準縮放,寬度超出 → 裁剪左右
        $scale = $target_h / $src_h;
        $scaled_w = (int)round($src_w * $scale);
        $scaled_h = $target_h;
        $src_x = (int)(($scaled_w - $target_w) / 2);
        $src_y = 0;
    } else {
        // 目標更高:源圖以寬為準縮放,高度超出 → 裁剪上下
        $scale = $target_w / $src_w;
        $scaled_w = $target_w;
        $scaled_h = (int)round($src_h * $scale);
        $src_x = 0;
        $src_y = (int)(($scaled_h - $target_h) / 2);
    }
    
    $src_img = load_image($src, $type);
    // 中間畫布:縮放後的完整圖
    $mid_img = imagecreatetruecolor($scaled_w, $scaled_h);
    copy_transparency($src_img, $mid_img, $type);
    imagecopyresampled($mid_img, $src_img, 0, 0, 0, 0, $scaled_w, $scaled_h, $src_w, $src_h);
    imagedestroy($src_img);
    
    // 目標畫布:裁剪目標區域
    $dst_img = imagecreatetruecolor($target_w, $target_h);
    imagecopy($dst_img, $mid_img, 0, 0, $src_x, $src_y, $target_w, $target_h);
    imagedestroy($mid_img);
    
    save_image($dst_img, $dst, $type, $quality);
    imagedestroy($dst_img);
    return true;
}

方法四:封裝類設計(支持水印、多模式、鏈式調用)

生產環境推薦面向對象封裝,代碼復用性高、擴展性強。

<?php
class ThumbnailGenerator {
    private $src_img;
    private $src_w;
    private $src_h;
    private $type;
    private $mime;
    
    // 鏈式調用配置
    private $mode = 'fit';      // fit | canvas | cover
    private $target_w = 200;
    private $target_h = 200;
    private $bg_color = '#FFFFFF';
    private $quality = 90;
    private $watermark = null;  // ['text'=>'...', 'font'=>'...', 'color'=>..., 'pos'=>...]
    
    public static function fromFile(string $path): self {
        $instance = new self();
        $info = getimagesize($path);
        if (!$info) throw new Exception("Invalid image: $path");
        [$instance->src_w, $instance->src_h, $instance->type] = $info;
        $instance->mime = $info['mime'] ?? '';
        $instance->src_img = $instance->loadImage($path, $instance->type);
        return $instance;
    }
    
    // 模式設置
    public function fit(int $w, int $h): self { $this->mode='fit'; $this->target_w=$w; $this->target_h=$h; return $this; }
    public function canvas(int $w, int $h, string $bg='#FFFFFF'): self { $this->mode='canvas'; $this->target_w=$w; $this->target_h=$h; $this->bg_color=$bg; return $this; }
    public function cover(int $w, int $h): self { $this->mode='cover'; $this->target_w=$w; $this->target_h=$h; return $this; }
    
    public function quality(int $q): self { $this->quality = $q; return $this; }
    
    // 文字水印
    public function textWatermark(string $text, string $font, array $color, string $pos='bottom-right', int $size=16, int $offset=10): self {
        $this->watermark = compact('text','font','color','pos','size','offset');
        return $this;
    }
    
    // 圖片水印
    public function imageWatermark(string $wm_path, string $pos='bottom-right', int $offset=10, float $opacity=1.0): self {
        $this->watermark = ['image'=>$wm_path, 'pos'=>$pos, 'offset'=>$offset, 'opacity'=>$opacity] + $this->watermark;
        return $this;
    }
    
    // 執行生成
    public function save(string $dst_path): bool {
        $dst_img = $this->process();
        $this->applyWatermark($dst_img);
        $this->saveImage($dst_img, $dst_path);
        imagedestroy($dst_img);
        return true;
    }
    
    // 核心處理邏輯
    private function process() {
        switch ($this->mode) {
            case 'fit':    return $this->processFit();
            case 'canvas': return $this->processCanvas();
            case 'cover':  return $this->processCover();
        }
    }
    
    private function processFit() {
        $ratio = min($this->target_w / $this->src_w, $this->target_h / $this->src_h);
        $w = (int)round($this->src_w * $ratio);
        $h = (int)round($this->src_h * $ratio);
        $dst = imagecreatetruecolor($w, $h);
        $this->copyWithTransparency($dst, 0, 0, $w, $h);
        return $dst;
    }
    
    private function processCanvas() {
        $dst = imagecreatetruecolor($this->target_w, $this->target_h);
        $this->fillBackground($dst);
        
        $src_ratio = $this->src_w / $this->src_h;
        $dst_ratio = $this->target_w / $this->target_h;
        
        if ($dst_ratio > $src_ratio) {
            $h = $this->target_h;
            $w = (int)round($h * $src_ratio);
            $x = (int)(($this->target_w - $w) / 2);
            $y = 0;
        } else {
            $w = $this->target_w;
            $h = (int)round($w / $src_ratio);
            $x = 0;
            $y = (int)(($this->target_h - $h) / 2);
        }
        $this->copyWithTransparency($dst, $x, $y, $w, $h);
        return $dst;
    }
    
    private function processCover() {
        $src_ratio = $this->src_w / $this->src_h;
        $dst_ratio = $this->target_w / $this->target_h;
        
        if ($dst_ratio > $src_ratio) {
            $scale = $this->target_h / $this->src_h;
            $mid_w = (int)round($this->src_w * $scale);
            $mid_h = $this->target_h;
            $src_x = (int)(($mid_w - $this->target_w) / 2);
            $src_y = 0;
        } else {
            $scale = $this->target_w / $this->src_w;
            $mid_w = $this->target_w;
            $mid_h = (int)round($this->src_h * $scale);
            $src_x = 0;
            $src_y = (int)(($mid_h - $this->target_h) / 2);
        }
        
        $mid = imagecreatetruecolor($mid_w, $mid_h);
        $this->copyWithTransparency($mid, 0, 0, $mid_w, $mid_h);
        
        $dst = imagecreatetruecolor($this->target_w, $this->target_h);
        imagecopy($dst, $mid, 0, 0, $src_x, $src_y, $this->target_w, $this->target_h);
        imagedestroy($mid);
        return $dst;
    }
    
    // 輔助方法
    private function loadImage($path, $type) {
        return match($type) {
            IMAGETYPE_JPEG => imagecreatefromjpeg($path),
            IMAGETYPE_PNG  => imagecreatefrompng($path),
            IMAGETYPE_GIF  => imagecreatefromgif($path),
            IMAGETYPE_WEBP => imagecreatefromwebp($path),
            default => throw new Exception("Unsupported type: $type"),
        };
    }
    
    private function copyWithTransparency($dst, $dx, $dy, $dw, $dh) {
        if (in_array($this->type, [IMAGETYPE_PNG, IMAGETYPE_GIF, IMAGETYPE_WEBP])) {
            imagealphablending($dst, false);
            imagesavealpha($dst, true);
            $tp = imagecolorallocatealpha($dst, 255,255,255,127);
            imagefill($dst, 0, 0, $tp);
        }
        imagecopyresampled($dst, $this->src_img, $dx, $dy, 0, 0, $dw, $dh, $this->src_w, $this->src_h);
    }
    
    private function fillBackground($img) {
        $hex = ltrim($this->bg_color, '#');
        if (strlen($hex) === 3) $hex = $hex[0].$hex[0].$hex[1].$hex[1].$hex[2].$hex[2];
        $r = hexdec(substr($hex,0,2));
        $g = hexdec(substr($hex,2,2));
        $b = hexdec(substr($hex,4,2));
        $c = imagecolorallocate($img, $r, $g, $b);
        imagefill($img, 0, 0, $c);
    }
    
    private function applyWatermark($img) {
        if (!$this->watermark) return;
        // 文字水印實現略...
        // 圖片水印實現略...
    }
    
    private function saveImage($img, $path) {
        $ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
        $func = match($ext) {
            'jpg','jpeg' => fn() => imagejpeg($img, $path, $this->quality),
            'png'        => fn() => imagepng($img, $path, (int)(9 - $this->quality/10)),
            'gif'        => fn() => imagegif($img, $path),
            'webp'       => fn() => imagewebp($img, $path, $this->quality),
            default      => throw new Exception("Unsupported output format: $ext"),
        };
        $func();
    }
    
    public function __destruct() {
        if ($this->src_img) imagedestroy($this->src_img);
    }
}

// ===== 使用示例 =====
ThumbnailGenerator::fromFile('photo.jpg')
    ->cover(400, 300)
    ->quality(90)
    ->textWatermark('© MySite', __DIR__.'/fonts/Roboto.ttf', [255,255,255,100], 'bottom-right', 14)
    ->save('thumbs/photo_cover.jpg');

四大方法對比選型指南

方法關鍵字特點適用場景輸出尺寸
等比縮放fit / contain完整顯示、不變形、尺寸不固定列表圖、文章配圖、原圖預覽≤ 目標框
固定畫布居中canvas / pad尺寸固定、不變形、留白填充背景色商品主圖、相冊網格、輪播圖統一尺寸= 目標框
填滿裁剪cover / crop尺寸固定、不變形、無留白、居中裁剪Banner、封面、頭像方框、Hero 圖= 目標框
強制拉伸stretch尺寸固定、可能變形極少用,僅特殊佔位圖= 目標框

核心輔助函數庫(上述代碼依賴)

function save_image($img, $path, $type, $quality) {
    $func = match($type) {
        IMAGETYPE_JPEG => fn() => imagejpeg($img, $path, $quality),
        IMAGETYPE_PNG  => fn() => imagepng($img, $path, (int)(9 - $quality/10)),
        IMAGETYPE_GIF  => fn() => imagegif($img, $path),
        IMAGETYPE_WEBP => fn() => imagewebp($img, $path, $quality),
        default => throw new Exception("Unsupported save type"),
    };
    $func();
}

function copy_transparency($src, $dst, $type) {
    if (in_array($type, [IMAGETYPE_PNG, IMAGETYPE_GIF, IMAGETYPE_WEBP])) {
        imagealphablending($dst, false);
        imagesavealpha($dst, true);
        $tp = imagecolorallocatealpha($dst, 255,255,255,127);
        imagefill($dst, 0, 0, $tp);
    }
}

常見坑點總結

  1. imagecopyresampled 參數順序:dst, src, dst_x, dst_y, src_x, src_y, dst_w, dst_h, src_w, src_h —— 目標在前、源在後,寬高對應。
  2. 透明度處理必須在 imagecopyresampled 之前**:先設置目標畫布透明屬性、填充透明色,再繪製。
  3. PNG 壓縮級 0-9**:0 無壓縮(快、大),9 最大壓縮(慢、小),6 平衡。imagepng($img, $path, 6)。
  4. JPEG 質量 0-100**:90-95 高質量,75-85 Web 標準,<60 明顯失真。
  5. 內存洩漏**:每個 imagecreate* 必須配對 imagedestroy(),類模式用 __destruct 保底。
  6. 大圖內存溢出**:GD 解壓到內存,4000×3000 ≈ 48MB。批量處理需增大 memory_limit 或用 Imagick。

總結

PHP 縮略圖生成的核心在于「幾何計算(比例/座標)」+「GD 繪圖管線(載入→畫布→重採樣→輸出)」。掌握 **fit/canvas/cover** 三種模式的數學邏輯,配合 **透明度保持**、**水印疊加**、**多格式輸出** 等工程化細節,即可構建滿足各類業務需求的圖片處理體系。推薦採用 ThumbnailGenerator 類封裝,鏈式調用、單一職責、易測試、易擴展。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved