PicThumb 是功能完整的縮圖產生類,支援 ImageMagick(效能佳、功能全)與 GD(相容性佳)雙引擎,提供等比縮放、裁剪、背景填充、浮水印等功能。
| 功能 | ImageMagick | GD |
|---|---|---|
| 等比縮放 (fit) | ✓ | ✓ |
| 區域裁剪 (crop) | ✓ | ✓ |
| 背景填充 | ✓ | ✓ |
| 浮水印 (透明度/位置) | ✓ 完整支援 | ⚠ 僅支援不透明水印 |
| CMYK 轉 RGB | ✓ 自動處理 | ✗ |
| GIF 動畫處理 | ✓ | ✗ |
<?php
/** 縮圖生成類,支援 ImageMagick 及 GD 庫
* Date: 2013-07-15
* Author: fdipzone
* Ver: 1.2
*/
class PicThumb {
private $_log = null;
private $_handler = null; // imagemagick / gd
private $_type = 'fit'; // fit / crop
private $_source = null;
private $_dest = null;
private $_watermark = null;
private $_opacity = 75;
private $_gravity = 'SouthEast';
private $_geometry = '+10+10';
private $_croppos = 'TL';
private $_bgcolor = null;
private $_quality = 90;
private $_width = null;
private $_height = null;
public function __construct($logfile = '') {
if ($logfile) $this->_log = $logfile;
}
/** 設定參數 */
public function set_config($param = array()) {
$this->_handler = $this->exists($param, 'handler') ? strtolower($param['handler']) : null;
$this->_type = $this->exists($param, 'type') ? strtolower($param['type']) : 'fit';
$this->_watermark = $this->exists($param, 'watermark') ? $param['watermark'] : null;
$this->_opacity = $this->exists($param, 'opacity') ? $param['opacity'] : 75;
$this->_gravity = $this->exists($param, 'gravity') ? $param['gravity'] : 'SouthEast';
$this->_geometry = $this->exists($param, 'geometry') ? $param['geometry'] : '+10+10';
$this->_croppos = $this->exists($param, 'croppos') ? $param['croppos'] : 'TL';
$this->_bgcolor = $this->exists($param, 'bgcolor') ? $param['bgcolor'] : null;
$this->_quality = $this->exists($param, 'quality') ? $param['quality'] : 90;
$this->_width = $this->exists($param, 'width') ? $param['width'] : null;
$this->_height = $this->exists($param, 'height') ? $param['height'] : null;
}
/** 產生縮圖主入口 */
public function create_thumb($source, $dest) {
if (!$this->check_handler()) { $this->to_log('handler not installed'); return false; }
if (!is_numeric($this->_width) || !is_numeric($this->_height) || $this->_width <= 0 || $this->_height <= 0) {
$this->to_log('width or height invalid'); return false;
}
if (!file_exists($source)) { $this->to_log($source . ' not exists'); return false; }
if (!$this->create_dirs($dest)) { $this->to_log(dirname($dest) . ' create fail'); return false; }
$this->_source = $source;
$this->_dest = $dest;
switch ($this->_type) {
case 'fit': return $this->_handler == 'imagemagick' ? $this->fit() : $this->gd_fit();
case 'crop': return $this->_handler == 'imagemagick' ? $this->crop() : $this->gd_crop();
default: $this->to_log('unknown type'); return false;
}
}
/** ImageMagick 等比縮放 */
private function fit() {
$bgcolor = $this->_bgcolor ? sprintf(" -background '%s' -gravity center -extent '%sx%s' ", $this->_bgcolor, $this->_width, $this->_height) : "";
$source_info = getimagesize($this->_source);
$colorspace = (!isset($source_info['channels']) || $source_info['channels'] != 3) ? ' -colorspace RGB ' : '';
$cmd = sprintf("convert -resize '%sx%s' '%s' %s -quality %s %s '%s'",
$this->_width, $this->_height, $this->_source, $bgcolor, $this->_quality, $colorspace, $this->_dest);
$this->to_log($cmd);
exec($cmd);
$this->add_watermark($this->_dest);
return is_file($this->_dest);
}
/** ImageMagick 裁剪 */
private function crop() {
list($pic_w, $pic_h) = $this->get_size();
list($offset_w, $offset_h) = $this->get_crop_offset($pic_w, $pic_h);
$source_info = getimagesize($this->_source);
$colorspace = (!isset($source_info['channels']) || $source_info['channels'] != 3) ? ' -colorspace RGB ' : '';
$cmd = sprintf("convert -resize '%sx%s' '%s' -quality %s %s -crop %sx%s+%s+%s +repage '%s'",
$pic_w, $pic_h, $this->_source, $this->_quality, $colorspace,
$this->_width, $this->_height, $offset_w, $offset_h, $this->_dest);
$this->to_log($cmd);
exec($cmd);
$this->add_watermark($this->_dest);
return is_file($this->_dest);
}
/** GD 等比縮放 */
private function gd_fit() {
list($pic_w, $pic_h) = $this->get_size();
list($ow, $oh, $otype) = getimagesize($this->_source);
$source_img = $this->create_source_image($this->_source, $otype);
$new_img = imagecreatetruecolor($pic_w, $pic_h);
imagecopyresampled($new_img, $source_img, 0, 0, 0, 0, $pic_w, $pic_h, $ow, $oh);
if ($this->_bgcolor) {
$bg_img = imagecreatetruecolor($this->_width, $this->_height);
$rgb = $this->hex2rgb($this->_bgcolor);
$bgcolor = imagecolorallocate($bg_img, $rgb['r'], $rgb['g'], $rgb['b']);
imagefill($bg_img, 0, 0, $bgcolor);
imagecopy($bg_img, $new_img, (int)(($this->_width - $pic_w)/2), (int)(($this->_height - $pic_h)/2), 0, 0, $pic_w, $pic_h);
$new_img = $bg_img;
}
$this->save_image($new_img, $this->_dest);
$this->add_watermark($this->_dest);
return is_file($this->_dest);
}
/** GD 裁剪 */
private function gd_crop() {
list($pic_w, $pic_h) = $this->get_size();
list($offset_w, $offset_h) = $this->get_crop_offset($pic_w, $pic_h);
list($ow, $oh, $otype) = getimagesize($this->_source);
$source_img = $this->create_source_image($this->_source, $otype);
$tmp_img = imagecreatetruecolor($pic_w, $pic_h);
imagecopyresampled($tmp_img, $source_img, 0, 0, 0, 0, $pic_w, $pic_h, $ow, $oh);
$new_img = imagecreatetruecolor($this->_width, $this->_height);
imagecopyresampled($new_img, $tmp_img, 0, 0, $offset_w, $offset_h, $this->_width, $this->_height, $this->_width, $this->_height);
$this->save_image($new_img, $this->_dest);
$this->add_watermark($this->_dest);
return is_file($this->_dest);
}
/** 計算目標尺寸 */
private function get_size() {
list($ow, $oh) = getimagesize($this->_source);
$w = (int)$this->_width; $h = (int)$this->_height;
if ($this->_type == 'fit') {
$pic_w = $w; $pic_h = (int)($pic_w * $oh / $ow);
if ($pic_h > $h) { $pic_h = $h; $pic_w = (int)($pic_h * $ow / $oh); }
} else { // crop
if ($ow > $oh) { $pic_h = $h; $pic_w = (int)($pic_h * $ow / $oh); }
else { $pic_w = $w; $pic_h = (int)($pic_w * $oh / $ow); }
}
return array($pic_w, $pic_h);
}
/** 計算裁剪偏移 */
private function get_crop_offset($pic_w, $pic_h) {
$ow = $oh = 0;
switch (strtoupper($this->_croppos)) {
case 'TL': $ow = 0; $oh = 0; break;
case 'TM': $ow = (int)(($pic_w - $this->_width)/2); $oh = 0; break;
case 'TR': $ow = $pic_w - $this->_width; $oh = 0; break;
case 'ML': $ow = 0; $oh = (int)(($pic_h - $this->_height)/2); break;
case 'MM': $ow = (int)(($pic_w - $this->_width)/2); $oh = (int)(($pic_h - $this->_height)/2); break;
case 'MR': $ow = $pic_w - $this->_width; $oh = (int)(($pic_h - $this->_height)/2); break;
case 'BL': $ow = 0; $oh = $pic_h - $this->_height; break;
case 'BM': $ow = (int)(($pic_w - $this->_width)/2); $oh = $pic_h - $this->_height; break;
case 'BR': $ow = $pic_w - $this->_width; $oh = $pic_h - $this->_height; break;
}
return array($ow, $oh);
}
/** 加浮水印 */
private function add_watermark($dest) {
if (!$this->_watermark || !file_exists($this->_watermark) || !file_exists($dest)) return;
list($ow, $oh, $ot) = getimagesize($dest);
list($ww, $wh, $wt) = getimagesize($this->_watermark);
if ($ww > $ow || $wh > $oh) return;
if ($this->_handler == 'imagemagick') {
$cmd = sprintf("composite -gravity %s -geometry %s -dissolve %s '%s' %s %s",
$this->_gravity, $this->_geometry, $this->_opacity, $this->_watermark, $dest, $dest);
$this->to_log($cmd);
exec($cmd);
} else {
// GD 水印(不支援透明度)
$water_img = $this->create_source_image($this->_watermark, $wt);
$dest_img = $this->create_source_image($dest, $ot);
list($posX, $posY) = $this->calc_watermark_pos($ow, $oh, $ww, $wh);
imagealphablending($dest_img, true);
imagecopy($dest_img, $water_img, $posX, $posY, 0, 0, $ww, $wh);
$this->save_image($dest_img, $dest);
imagedestroy($water_img); imagedestroy($dest_img);
}
}
/** 檢查處理引擎 */
private function check_handler() {
$im = (strstr(shell_exec('convert -version'), 'Version: ImageMagick') !== false);
$gd = function_exists('gd_info');
if ($this->_handler === 'imagemagick') return $im;
if ($this->_handler === 'gd') return $gd;
if ($im) { $this->_handler = 'imagemagick'; return true; }
if ($gd) { $this->_handler = 'gd'; return true; }
return false;
}
/** 輔助方法:建立來源圖片資源 */
private function create_source_image($file, $type) {
switch ($type) {
case 1: return imagecreatefromgif($file);
case 2: return imagecreatefromjpeg($file);
case 3: return imagecreatefrompng($file);
default: return false;
}
}
/** 輔助方法:儲存圖片 */
private function save_image($img, $dest) {
$ext = $this->get_file_ext($dest);
switch ($ext) {
case 1: imagegif($img, $dest, $this->_quality); break;
case 2: imagejpeg($img, $dest, $this->_quality); break;
case 3: imagepng($img, $dest, (int)(($this->_quality - 1) / 10)); break;
}
}
/** 輔助方法:計算水印位置 */
private function calc_watermark_pos($ow, $oh, $ww, $wh) {
switch (strtolower($this->_gravity)) {
case 'northwest': return array(0, 0);
case 'north': return array(($ow - $ww) / 2, 0);
case 'northeast': return array($ow - $ww, 0);
case 'west': return array(0, ($oh - $wh) / 2);
case 'center': return array(($ow - $ww) / 2, ($oh - $wh) / 2);
case 'east': return array($ow - $ww, ($oh - $wh) / 2);
case 'southwest': return array(0, $oh - $wh);
case 'south': return array(($ow - $ww) / 2, $oh - $wh);
case 'southeast': return array($ow - $ww, $oh - $wh);
}
return array(0, 0);
}
/** 其他輔助方法:create_dirs, exists, to_log, hex2rgb, get_file_ext... */
}
?>
<?php
require('PicThumb.class.php');
$logfile = __DIR__ . '/PicThumb.log';
$source = __DIR__ . '/pic/source.jpg';
$watermark = __DIR__ . '/pic/watermark.png';
// 1. 等比縮圖 100x100
$obj = new PicThumb($logfile);
$obj->set_config(array('type'=>'fit', 'width'=>100, 'height'=>100));
$obj->create_thumb($source, __DIR__ . '/pic/1.jpg');
// 2. 等比縮圖 + 紅色背景填充
$obj->set_config(array('type'=>'fit', 'width'=>100, 'height'=>100, 'bgcolor'=>'#FF0000'));
$obj->create_thumb($source, __DIR__ . '/pic/2.gif');
// 3. 裁剪 250x250 (底部置中) + 水印 (西南角, 透明度 50%)
$obj->set_config(array(
'type'=>'crop', 'croppos'=>'BM', 'width'=>250, 'height'=>250,
'watermark'=>$watermark, 'opacity'=>50, 'gravity'=>'SouthWest'
));
$obj->create_thumb($source, __DIR__ . '/pic/3.png');
// 4. CMYK 圖片自動轉 RGB 處理
$source_cmyk = __DIR__ . '/pic/source_cmyk.jpg';
$obj->set_config(array('type'=>'fit', 'width'=>100, 'height'=>100));
$obj->create_thumb($source_cmyk, __DIR__ . '/pic/4.jpg');
?>