前段时间,由于项目中页面速度加载太慢,需要进行优化,很重要的一个原因是页面加载图片偏多,而图片放在合作方服务器中且未经过压缩。我需要做的就是把图片从对方服务器拉取下来,存到我们公司的图片服务器上,然后对图片进行压缩。
从对方服务器上拉取下来的图片有5000多张,文件后缀名均为jpg。我采用PHP中的GD库对图片进行压缩,刚开始直接根据文件后缀名使用对应的imagecreatefromjpeg()、imagecreatefromgif()、imagecreatefrompng()生成相应的句柄。
$img = '1.jpg';
$ext = strtoupper(pathinfo($img, PATHINFO_EXTENSION));
if (is_file($img) && ($ext == 'JPG' || $ext == 'JPEG')) {
$imgInput = @imagecreatefromjpeg($img);
} else if (is_file($img) && ($ext == 'PNG')) {
$imgInput = @imagecreatefrompng($img);
} else if (is_file($img) && ($ext == 'GIF')) {
$imgInput = @imagecreatefromgif($img);
}
但是,在最终压缩完成的图片中,发现有十几张图片是压缩失败的。那究竟是为什么呢?
尝试用Photoshop打开压缩失败的图片,得到如下的提示:
原来虽然下载下来的图片后缀是jpg,实际上其中有一些图片格式并不是jpg,需要采用其他方法来识别图片的类型。
使用exif_imagetype()来判断图片类型,exif_imagetype()读取图像的第一个字节并检查其签名,通过这种识别可以减少误判断。
首先创建一个类ImageCompress,该类是一个工具类,采用单例模式,主要框图如下:
class ImageCompress
{
private $_imgInput;//图片输入句柄
private $_imgOutput; //图片输出句柄
private $_imgSrc; //图片源路径
private $_format; //图片格式
private $_quality = 80; //图片压缩质量,默认为80
private $_xInput; //图片原始宽度
private $_yInput; //图片原始高度
private $_xOutput; //图片输出宽度
private $_yOutput; //图片输出高度
private $_resize; //图片是否需要压缩
private static $_handler = null; //单例模式句柄
/**
* 单例入口
* @return ImageCompress
*/
public static function getInstance()
{
if (is_null(self::$_handler)) {
self::$_handler = new self();
}
return self::$_handler;
}
/**
* 防止被复制
*/
private function __clone()
{
}
/**
* 防止被外部实例
*/
private function __construct()
{
}
/**
* 设置压缩质量
* @param unknown $quality
*/
public function setQuality($quality)
{
if (is_int($quality)) {
$this->_quality = $quality;
}
}
/**
* 获取原始图片宽度
* @return number
*/
public function getWidth()
{
return $this->_xInput;
}
/**
* 获取原始图片高度
* @return number
*/
public function getHeight()
{
return $this->_yInput;
}
/**
* 清楚图片缓存
*/
public function clearCache()
{
@imagedestroy($this->_imgInput);
@imagedestroy($this->_imgOutput);
}
/**
* 回收
*/
public function __destruct()
{
$this->clearCache();
}
/**
* 设置要处理的图片
* @param string $img
* @return boolean
*/
public function setImg($img){}
/**
* 设置图片最大宽高
* @param number $width
* @param number $height
*/
public function setMaxSize($width = 100, $height = 100){}
/**
* 设置图片最小宽高
* @param number $width
* @param number $height
*/
public function setMinSize($width = 300, $height = 300){}
/**
* 保存图片
* @param unknown $savedPath
* @return boolean
*/
public function saveImg($savedPath){}
}
setImg()方法中是根据exif_imagetype()来识别图片类型的,使用exif_imagetype()需要php启用exif支持,详见:http://php.net/manual/en/exif.installation.php
/**
* 设置要处理的图片
* @param string $img
* @return boolean
*/
public function setImg($img)
{
$this->_imgSrc = $img;
if (is_file($img)) {
switch (exif_imagetype($img)) {
case IMAGETYPE_GIF: {
$this->_format = 'GIF';
$this->_imgInput = @imagecreatefromgif($img);
break;
}
case IMAGETYPE_JPEG: {
$this->_format = 'JPG';
$this->_imgInput = @imagecreatefromjpeg($img);
break;
}
case IMAGETYPE_PNG: {
$this->_format = 'PNG';
$this->_imgInput = @imagecreatefrompng($img);
break;
}
default:{
return false;
}
}
} else {
die($img . ' cannot be found');
}
if (empty($this->_imgInput)) {
die($img . ' cannot be initial');
}
$this->_xInput = @imagesx($this->_imgInput);
$this->_yInput = @imagesy($this->_imgInput);
return true;
}
setMaxSize()方法用于设置图片的最大宽高,根据图片的宽高压缩比大小决定最终的图片宽高。
/**
* 设置图片最大宽高
* @param number $width
* @param number $height
*/
public function setMaxSize($width = 100, $height = 100)
{
if ($this->_xInput > $width || $this->_yInput > $height) {
$resizeWidth = false;
$resizeHeight = false;
$ratio = 1;
if ($this->_xInput > $width) {
$widthRatio = $width / $this->_xInput;
$resizeWidth = true;
}
if ($this->_yInput > $height) {
$heightRatio = $height / $this->_yInput;
$resizeHeight = true;
}
if ($resizeWidth) {
$ratio = $widthRatio;
}
if ($resizeHeight) {
$ratio = $heightRatio;
}
if ($resizeHeight && $resizeWidth) {
if ($widthRatio < $heightRatio) {
$ratio = $widthRatio;
} else {
$ratio = $heightRatio;
}
}
$this->_xOutput = $this->_xInput * $ratio;
$this->_yOutput = $this->_yInput * $ratio;
$this->_resize = true;
} else {
$this->_resize = false;
}
}
setMinSize()方法是用于设置图片的最小宽高,保证图片宽高的最小值
/**
* 设置图片最小宽高
* @param number $width
* @param number $height
*/
public function setMinSize($width = 300, $height = 300)
{
if (($this->_xInput == $width && $this->_yInput >= $height) || ($this->_xInput >= $width && $this->_yInput == $height)) {
$this->_resize = false;
} else {
$ratio = 1;
$widthRatio = $width / $this->_xInput;
$heightRatio = $height / $this->_yInput;
if ($widthRatio > $heightRatio) {
$ratio = $widthRatio;
} else {
$ratio = $heightRatio;
}
$this->_xOutput = $this->_xInput * $ratio;
$this->_yOutput = $this->_yInput * $ratio;
$this->_resize = true;
}
}
说到setMinSize()这个方法,就不得不提微信分享中的图标,如果页面中没有300*300以上的图片,微信会加载默认的图标,如下图所示:
如果页面中存在多个300300的图标,无论隐藏与否,微信会默认使用第一张300300作为分享中的图标。当时页面中的图片没有300300,如果直接在DOM中插入一张300300的图片,会使非微信情况下多产生一条http链接。我的解决方法:页面加载完成后,根据UA判断是否为微信,若为微信,则动态请求图片,插入到DOM中,并设置为隐藏状态。
saveImg()方法用于保存图片
/**
* 保存图片
* @param string $savedPath
* @return boolean
*/
public function saveImg($savedPath)
{
$saved = false;
if ($this->_resize) {
$this->_imgOutput = imagecreatetruecolor($this->_xOutput, $this->_yOutput);
imagecopyresampled($this->_imgOutput, $this->_imgInput, 0, 0, 0, 0, $this->_xOutput, $this->_yOutput, $this->_xInput, $this->_yInput);
}
if ($this->_format == 'JPG') {
if ($this->_resize) {
$saved = imagejpeg($this->_imgOutput, $savedPath, $this->_quality);
} else {
$saved = @copy($this->_imgSrc, $savedPath);
}
} else if ($this->_format == 'PNG') {
if ($this->_resize) {
$saved = imagepng($this->_imgOutput, $savedPath, $this->_quality / 10);
} else {
$saved = @copy($this->_imgSrc, $savedPath);
}
} else if ($this->_format == 'GIF') {
if ($this->_resize) {
$saved = imagegif($this->_imgOutput, $savedPath);
}
}
return $saved;
}
至此,整个ImageCompress类构建完成,用法如下:
$imgCmp = ImageCompress::getInstance();
$imgCmp->setImg("1.jpg");
$imgCmp->setMaxSize(200, 200);
$imgCmp->saveImg("2.jpg");
$imgCmp->setMinSize(300, 300);
$imgCmp->saveImg("3.jpg");
$imgCmp->clearCache();