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

PHP GD 圖形處理實戰:驗證碼、圖片水印、縮圖生成、文字排版與 WebP 輸出

     編輯:PHP 綜合知識

GD 是 PHP 內建圖形庫,支援 JPEG/PNG/GIF/WebP/BMP,適合中等負載的圖片處理。本文涵蓋驗證碼生成、動態水印、智慧縮圖 (保持比例/裁剪/填充)、多行文字自動換行、字體子集嵌入、WebP 輸出優化,並解決中文亂碼、記憶體洩漏、座標計算陷阱。

一、環境檢查與字體準備

# Ubuntu/Debian
apt-get install -y php-gd fonts-noto-cjk fonts-dejavu-core
# 檢查
php -r 'var_dump(gd_info());' 
# 確認 FreeType Support => true, TTF Support => true

專案目錄結構:

/fonts/
  NotoSansTC-Regular.otf   # 繁體中文
  DejaVuSans.ttf           # 英文/符號 fallback
  NotoEmoji-Regular.ttf    # Emoji 支援 (可選)

二、驗證碼生成器 (支援干擾線、噪點、扭曲)

<?php
declare(strict_types=1);

namespace App\Image;

class CaptchaGenerator {
    public function __construct(
        private int $width = 160,
        private int $height = 50,
        private int $length = 5,
        private string $fontFile = __DIR__.'/../fonts/DejaVuSans.ttf',
        private int $fontSize = 24
    ) {}

    public function generate(): array {
        $code = $this->randomCode();
        $img = imagecreatetruecolor($this->width, $this->height);
        
        // 背景
        $bg = imagecolorallocate($img, 245, 245, 245);
        imagefill($img, 0, 0, $bg);
        
        // 干擾線
        for ($i = 0; $i < 5; $i++) {
            $color = imagecolorallocatealpha($img, rand(100,200), rand(100,200), rand(100,200), 80);
            imageline($img, rand(0,$this->width), rand(0,$this->height), rand(0,$this->width), rand(0,$this->height), $color);
        }
        // 噪點
        for ($i = 0; $i < 200; $i++) {
            $color = imagecolorallocatealpha($img, rand(0,255), rand(0,255), rand(0,255), 60);
            imagesetpixel($img, rand(0,$this->width), rand(0,$this->height), $color);
        }
        
        // 文字 (逐字隨機旋轉/位移)
        $x = 15;
        for ($i = 0; $i < $this->length; $i++) {
            $char = $code[$i];
            $angle = rand(-15, 15);
            $color = imagecolorallocate($img, rand(30,100), rand(30,100), rand(30,100));
            $bbox = imagettfbbox($this->fontSize, $angle, $this->fontFile, $char);
            $charW = $bbox[2] - $bbox[0];
            $y = $this->height/2 + ($bbox[1] - $bbox[7])/2 + rand(-3,3);
            imagettftext($img, $this->fontSize, $angle, $x, $y, $color, $this->fontFile, $char);
            $x += $charW + rand(5, 12);
        }
        
        ob_start();
        imagepng($img);
        $pngData = ob_get_clean();
        imagedestroy($img);
        
        return ['code' => strtolower($code), 'image' => $pngData];
    }
    
    private function randomCode(): string {
        $chars = '23456789ABCDEFGHJKLMNPQRSTUVWXYZ';
        return substr(str_shuffle($chars), 0, $this->length);
    }
}
?>

三、智慧縮圖:三種模式

<?php
class Thumbnailer {
    public enum Mode: string {
        case FIT = 'fit';       // 等比縮放,完整顯示,留白
        case COVER = 'cover';   // 等比縮放,填滿畫布,裁剪溢出
        case STRETCH = 'stretch'; // 強制拉伸填滿
    }
    
    public function resize(string $srcPath, string $dstPath, int $w, int $h, Mode $mode = Mode::FIT, int $quality = 85): void {
        [$srcW, $srcH, $type] = getimagesize($srcPath);
        $src = $this->createFromType($srcPath, $type);
        
        switch ($mode) {
            case Mode::FIT:
                $ratio = min($w/$srcW, $h/$srcH);
                $dstW = (int)round($srcW * $ratio);
                $dstH = (int)round($srcH * $ratio);
                $dstX = ($w - $dstW) / 2;
                $dstY = ($h - $dstH) / 2;
                break;
            case Mode::COVER:
                $ratio = max($w/$srcW, $h/$srcH);
                $dstW = (int)round($srcW * $ratio);
                $dstH = (int)round($srcH * $ratio);
                $srcX = ($dstW - $w) / 2 / $ratio;
                $srcY = ($dstH - $h) / 2 / $ratio;
                $srcW = $w / $ratio;
                $srcH = $h / $ratio;
                $dstX = 0; $dstY = 0;
                break;
            case Mode::STRETCH:
                $dstW = $w; $dstH = $h;
                $dstX = 0; $dstY = 0;
                break;
        }
        
        $dst = imagecreatetruecolor($w, $h);
        // 透明背景支援
        if (in_array($type, [IMAGETYPE_PNG, IMAGETYPE_GIF, IMAGETYPE_WEBP])) {
            imagealphablending($dst, false);
            imagesavealpha($dst, true);
            $transparent = imagecolorallocatealpha($dst, 0,0,0,127);
            imagefill($dst, 0, 0, $transparent);
        } else {
            $white = imagecolorallocate($dst, 255,255,255);
            imagefill($dst, 0, 0, $white);
        }
        
        if ($mode === Mode::COVER) {
            imagecopyresampled($dst, $src, $dstX, $dstY, $srcX, $srcY, $dstW, $dstH, $srcW, $srcH);
        } else {
            imagecopyresampled($dst, $src, $dstX, $dstY, 0, 0, $dstW, $dstH, $srcW, $srcH);
        }
        
        $this->saveByType($dst, $dstPath, $type, $quality);
        imagedestroy($src); imagedestroy($dst);
    }
    
    private function createFromType(string $path, int $type): \GdImage {
        return match($type) {
            IMAGETYPE_JPEG => imagecreatefromjpeg($path),
            IMAGETYPE_PNG => imagecreatefrompng($path),
            IMAGETYPE_GIF => imagecreatefromgif($path),
            IMAGETYPE_WEBP => imagecreatefromwebp($path),
            default => throw new \InvalidArgumentException('不支援的圖片類型'),
        };
    }
    private function saveByType(\GdImage $img, string $path, int $type, int $q): void {
        match($type) {
            IMAGETYPE_JPEG => imagejpeg($img, $path, $q),
            IMAGETYPE_PNG => imagepng($img, $path, (int)round($q/10)), // PNG 壓縮 0-9
            IMAGETYPE_WEBP => imagewebp($img, $path, $q),
            default => imagepng($img, $path),
        };
    }
}
?>

四、多行文字自動換行與對齊

<?php
function drawTextBox(\GdImage $img, string $text, int $x, int $y, int $boxW, int $boxH, string $font, int $size, array $color, int $align = 0, int $valign = 0, float $lineHeight = 1.5): void {
    // $align: 0=left, 1=center, 2=right; $valign: 0=top, 1=middle, 2=bottom
    $lines = mb_wordwrap($text, $boxW, $font, $size); // 自訂函數,按像素寬度換行
    $totalH = count($lines) * $size * $lineHeight;
    $startY = match($valign) {
        0 => $y,
        1 => $y + ($boxH - $totalH)/2,
        2 => $y + $boxH - $totalH,
    };
    $textColor = imagecolorallocatealpha($img, $color[0], $color[1], $color[2], $color[3] ?? 0);
    foreach ($lines as $i => $line) {
        $bbox = imagettfbbox($size, 0, $font, $line);
        $lineW = $bbox[2] - $bbox[0];
        $lineX = match($align) {
            0 => $x,
            1 => $x + ($boxW - $lineW)/2,
            2 => $x + $boxW - $lineW,
        };
        $lineY = $startY + $i * $size * $lineHeight + $size;
        imagettftext($img, $size, 0, $lineX, $lineY, $textColor, $font, $line);
    }
}

function mb_wordwrap(string $text, int $maxWidth, string $font, int $size): array {
    $words = preg_split('/(\s+)/u', $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
    $lines = [''];
    foreach ($words as $word) {
        $test = $lines[count($lines)-1] . $word;
        $bbox = imagettfbbox($size, 0, $font, $test);
        if ($bbox[2] - $bbox[0] <= $maxWidth) {
            $lines[count($lines)-1] = $test;
        } else {
            $lines[] = $word;
        }
    }
    return $lines;
}
?>

五、WebP 輸出與瀏覽器相容

<?php
function outputWebP(\GdImage $img, int $quality = 80): void {
    header('Content-Type: image/webp');
    header('Cache-Control: public, max-age=31536000');
    imagewebp($img, null, $quality); // null 直接輸出到瀏覽器
}

//  標籤優雅降級
echo '<picture>';
echo '<source type="image/webp" srcset="image.webp">';
echo '<img src="image.jpg" alt="描述">';
echo '</picture>';
?>

六、記憶體管理與效能提示

  1. 處理大圖 (超過 2000px) 時,ini_set('memory_limit', '512M') 並及時 imagedestroy()。
  2. 批次處理建議使用 Imagick (多執行緒、串流、格式支援更全),GD 單執行緒且全載入記憶體。
  3. 字體檔案快取:imagettfbbox 重複呼叫同字體同大小可快取 bounding box。
  4. 輸出前 ob_start() 避免標頭已送出錯誤。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved