專案中頻繁需要驗證碼、縮圖、動態文字圖片,與其每次重寫 GD 代碼,不如封裝可復用的 Img 類別。本文提供完整類別代碼(支援鏈式調用)、使用範例、擴充建議與常見坑點。
<?php
if (!defined('__WEBROOT__')) define('__WEBROOT__', $_SERVER['DOCUMENT_ROOT']);
class Img {
protected $_img; // 目標圖像資源
protected $_srcImg; // 來源圖像資源
protected $_srcInfo; // 來源圖像資訊 [width, height, type, ...]
protected $_rand = 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ23456789';
protected $_code = ''; // 驗證碼明文
// 公開屬性(可由 Style() 覆寫)
public $width = 100;
public $height = 30;
public $backgroundColor = '000000';
public $font = '/fonts/ARIALNB.TTF';
public $fontSize = 16;
public $fontColor = 'ffffff';
public $content = 'Test';
public $align = 'left';
public $codes = 4; // 驗證碼字元數
public $lines = 6; // 干擾線條數
public $loadErr = '';
/** 設定樣式(鏈式) */
public function Style(array $opts) {
$this->_reset();
foreach ($opts as $k => $v) {
if (property_exists($this, $k)) {
if (in_array($k, ['backgroundColor','fontColor'])) {
if (preg_match('/^[a-fA-F0-9]{6}$/', $v)) $this->$k = $v;
} else {
$this->$k = $v;
}
}
}
return $this;
}
protected function _reset() {
$this->width = 100; $this->height = 30;
$this->backgroundColor = '000000'; $this->fontSize = 16;
$this->fontColor = 'ffffff'; $this->align = 'left';
$this->codes = 4; $this->lines = 6;
}
/** 建立真彩畫布、填充背景 */
protected function _createCanvas($fillBg = true) {
$this->_img = imagecreatetruecolor($this->width, $this->height);
if ($fillBg) {
$c = $this->_hex2rgb($this->backgroundColor);
$bg = imagecolorallocate($this->_img, $c[0], $c[1], $c[2]);
imagefilledrectangle($this->_img, 0, 0, $this->width, $this->height, $bg);
}
}
protected function _hex2rgb($hex) {
return [hexdec(substr($hex,0,2)), hexdec(substr($hex,2,2)), hexdec(substr($hex,4,2))];
}
/** 輸出 PNG */
protected function _outputPng() {
header('Content-Type: image/png');
imagepng($this->_img);
imagedestroy($this->_img);
}
/** 輸出 JPEG */
protected function _outputJpeg() {
header('Content-Type: image/jpeg');
imagejpeg($this->_img, null, 90);
imagedestroy($this->_img);
}
/** 公開:建立文字圖片 (PNG) */
public function CreateImgPng() {
$this->_createCanvas();
$this->_writeText();
$this->_outputPng();
}
/** 公開:建立文字圖片 (JPEG) */
public function CreateImgJpeg() {
$this->_createCanvas();
$this->_writeText();
$this->_outputJpeg();
}
protected function _writeText() {
$box = imagettfbbox($this->fontSize, 0, __WEBROOT__.$this->font, $this->content);
$textW = $box[2] - $box[0];
$textH = $box[1] - $box[7];
$x = ($this->width - $textW) / 2;
$y = ($this->height + $textH) / 2;
$c = $this->_hex2rgb($this->fontColor);
$color = imagecolorallocate($this->_img, $c[0], $c[1], $c[2]);
imagettftext($this->_img, $this->fontSize, 0, $x, $y, $color, __WEBROOT__.$this->font, $this->content);
}
/** 公開:建立驗證碼圖片 */
public function CreateVerify() {
$this->backgroundColor = '';
for ($i=0;$i<3;$i++) $this->backgroundColor .= dechex(mt_rand(20,155));
$this->_createCanvas();
$this->_genCode();
$this->_drawLines();
$this->_writeCode();
$this->_outputPng();
}
protected function _genCode() {
$len = strlen($this->_rand) - 1;
for ($i=0;$i<$this->codes;$i++) $this->_code .= $this->_rand[mt_rand(0,$len)];
}
protected function _drawLines() {
for ($i=0;$i<$this->lines;$i++) {
$c = imagecolorallocate($this->_img, mt_rand(0,156), mt_rand(0,156), mt_rand(0,156));
imageline($this->_img, mt_rand(0,$this->width), mt_rand(0,$this->height),
mt_rand(0,$this->width), mt_rand(0,$this->height), $c);
}
}
protected function _writeCode() {
$charW = $this->width / $this->codes;
for ($i=0;$i<$this->codes;$i++) {
$c = imagecolorallocate($this->_img, mt_rand(0,156), mt_rand(0,156), mt_rand(0,156));
imagettftext($this->_img, $this->fontSize, mt_rand(-30,30),
$charW*$i + mt_rand(1,5), $this->height/1.3, $c,
__WEBROOT__.$this->font, $this->_code[$i]);
}
}
/** 取得驗證碼 MD5(儲存 Session 比對) */
public function GetVerify() { return md5($this->_code); }
/** 載入來源圖片 */
public function LoadImg($relPath) {
$abs = __WEBROOT__.$relPath;
if (!is_file($abs)) { $this->loadErr = '檔案不存在'; return false; }
$this->_srcInfo = getimagesize($abs);
$this->_srcInfo['path'] = $abs;
switch ($this->_srcInfo[2]) {
case IMAGETYPE_GIF: $this->_srcImg = imagecreatefromgif($abs); break;
case IMAGETYPE_JPEG: $this->_srcImg = imagecreatefromjpeg($abs); break;
case IMAGETYPE_PNG: $this->_srcImg = imagecreatefrompng($abs); break;
default: $this->loadErr = '不支援格式'; return false;
}
return true;
}
/** 建立縮圖並儲存 */
public function CreateThumb($prefix, $saveRelDir) {
if (!$this->_srcImg) return false;
$saveAbsDir = __WEBROOT__.$saveRelDir;
if (!is_dir($saveAbsDir)) mkdir($saveAbsDir, 0777, true);
$ext = image_type_to_extension($this->_srcInfo[2], false);
$filename = $prefix . date('YmdHis') . mt_rand(100,999) . '.' . $ext;
$savePath = $saveAbsDir . $filename;
$this->_createCanvas(false);
imagecopyresampled($this->_img, $this->_srcImg,
0,0,0,0, $this->width, $this->height,
$this->_srcInfo[0], $this->_srcInfo[1]);
switch ($this->_srcInfo[2]) {
case IMAGETYPE_GIF: imagegif($this->_img, $savePath); break;
case IMAGETYPE_JPEG: imagejpeg($this->_img, $savePath, 90); break;
case IMAGETYPE_PNG: imagepng($this->_img, $savePath); break;
}
imagedestroy($this->_img);
return ['filename'=>$filename, 'path'=>$saveRelDir.$filename];
}
}
<?php
require 'Img.php';
$img = new Img();
// 1. 產生文字圖片
$img->Style([
'width'=>300, 'height'=>100,
'content'=>'Hello PHP',
'fontColor'=>'FF0000',
'backgroundColor'=>'EEEEEE'
])->CreateImgPng();
// 2. 產生驗證碼並存 Session
session_start();
$img->Style(['width'=>120,'height'=>40,'codes'=>4,'lines'=>8])->CreateVerify();
$_SESSION['captcha'] = $img->GetVerify();
// 3. 縮圖
if ($img->LoadImg('/uploads/photo.jpg')) {
$info = $img->Style(['width'=>200,'height'=>200])->CreateThumb('thumb_', '/uploads/thumbs/');
var_dump($info); // ['filename'=>'thumb_20240115123045_123.jpg', 'path'=>'/uploads/thumbs/thumb_...']
} else {
die('載入失敗: '.$img->loadErr);
}
imagecopy() 疊加 Logo/文字imagefilledarc() 建立遮罩ob_start(); imagepng($img); $b64 = base64_encode(ob_get_clean()); 供 API 直接回傳 JSON__WEBROOT__.$this->font 必須為**絕對路徑**,相對路徑在 CLI/不同工作目錄下失效。imagecreatetruecolor 需 GD 2.0+,舊版主機確認支援。