起因

Mikusa同学的博客首页缩略图太大,影响加载速度。我建议他写一个缩略图裁切方法,以便在不需要原图的场合优化加载速度。他说他不会,我就写了。写完之后跟他的typecho主题结合了下,并发表(shui)了这篇文章。
对话

原理

每次调用时判断图片对应的缓存文件是否存在,如果存在则直接返回图片地址,如果不存在则切割一张图片并保存。(此原理源于宾果博客Beginning主题)

程序难点

由于图片的比例不同,如何选择裁剪区域是需要认真考虑的。如果都从(0,0)位置开始裁剪,可能凸显不了图片的重点。另外,如果裁切位置为固定位置,可能出现留白、留黑等裁切情况。

一个比较好的方法是当目标尺寸的宽/高比例大于实际比例时,将实际图片的宽度作为裁剪的宽度,裁剪的高度根据目标尺寸进行计算,此时裁剪起始位置的横坐标可以选定为(实际高度-裁剪所需要的高度)/2,纵坐标可以选定为0,这样可以保证裁剪区域为图像中心,并且包含目标尺寸比例内最大的图像范围。当目标尺寸的宽/高比例小于实际比例时,同理。

缩放裁剪使用imagecopyresampled方法。初用时查了查百度,后来发现好多人的参数详解压根不对,也导致我白折腾了好久。之后查了查官方文档,搞明白了这几个参数的含义。
imagecopyresampled

imagecopyresampled ( resource $dst_image , resource $src_image , int $dst_x , int $dst_y , int $src_x , int $src_y , int $dst_w , int $dst_h , int $src_w , int $src_h )
#参数详解:
//$dst_image:目标图像,把裁剪后图像保存到这里。
//$src_imag:源图像,被裁减的源图像。
//$dst_x与$dst_y:目标点,目标图像上的一个点,从这里开始填充裁剪后的信息。
//$src_x与$src_y:源点,源图像上的一个点,从这里开始取样。
//$dst_w与$dst_h:在目标图像上的一个区域范围,获得的取样信息将会填充到这个矩形里。
//$src_w与$src_h:在源图像上的一个区域范围,此矩形为取样区域,其内容将会填充到dst矩形里。

此部分的代码摘录如下:

$Des_scale = $imgWidth / $imgHeight; //目标缩放
$Origin_scale = $x / $y; //实际缩放
if ($Origin_scale > $Des_scale) //与目标比例相比,原始尺寸的宽度较大
{
    $thumbh = $y; //以100%高度为基准做裁切。
    $thumbw = $thumbh * $Des_scale; //高度为100%时对应的宽度
    $desCutPos_x = abs($thumbw - $x) / 2; //裁切位置
    $desCutPos_y = 0;            
} else {
    $thumbw = $x;
    $thumbh = $thumbw / $Des_scale;
    $desCutPos_x = 0;
    $desCutPos_y = abs($thumbh - $y) / 2;
}

完整代码及代码文件下载

点击下载

<?php
/**
 * Name: PHP图像裁切
 * Description: 可用于缩略图裁切等的图形裁切工具类
 * Version: 1.0.1
 * Author: 野兔|AT|梓喵出没
 * Author URI: https://www.azimiao.com
 */

# Typecho 获取网站链接
//ZMImgClip::$siteUrl = Helper::options()->siteUrl;

# 测试代码
ZMImgClip::$siteUrl = "https://localhost:8080/";
echo ZMImgClip::GetInstance()->ClipImage("./222.jpg",100,150,"local");
echo "\r\n";
echo ZMImgClip::GetInstance()->ClipImage("./222.jpg");
echo "\r\n";
echo ZMImgClip::GetInstance()->ClipImage("./111.png",0,0);
# end 测试代码

//o0o00o00oo0o0oo00o0
class ZMImgClip {
    //对象
    private static $imgCliper = null;
    //默认裁切尺寸
    private static $imgWidth = 300;//宽度
    private static $imgHeight = 300;//高度
    //图片缓存文件夹
    private static $tempCachePath = "usr/Temp/img/";
    //缩略图缓存文件夹 (位于图片缓存文件夹下)
    private static $imgPath = "smImg/";
    //错误图片 (如果提供的图片地址有误,则返回这个地址)
    private static $imgErrorPath = "//www.himiku.com/usr/themes/Yodu/images/load.gif";
    //网站URL 如果14行获取的URL不正确,则注释第14行,并在此填入正确的网址。
    public static $siteUrl = "";

    /**
     * 获取对象
     */
    public static function GetInstance() {
        if (ZMImgClip::$imgCliper == null) {
            ZMImgClip::$imgCliper = new ZMImgClip();
        }
        return ZMImgClip::$imgCliper;
    }

    /**
     * 获取路径
     */
    private function GetImgPath($fileName = "", $type = "folder") {
        $url = "";
        switch ($type) {
            default:
            case 'local':
                $url .= ZMImgClip::$tempCachePath;
                $url .= ZMImgClip::$imgPath;
                $url .= $fileName;
            break;
            case "url":
                $url .= ZMImgClip::$siteUrl;
                $url .= ZMImgClip::$tempCachePath;
                $url .= ZMImgClip::$imgPath;
                $url .= $fileName;
            break;
            case "folder":
                $url .= ZMImgClip::$tempCachePath;
                $url .= ZMImgClip::$imgPath;
            break;
        }
        return $url;
    }

    /**
     * 获取缓存图片文件名
     */
    private function GetImgName($imgPath, $width, $height) {
        $name = "";
        $name .= md5($imgPath);
        $name .= "-{$width}-{$height}.jpg";
        return $name;
    }

    /**
     * 上传至七牛
     */
    private function FileUpToQiNiu($localPath,$auth){
        //TODO 
    }

    /**
     * 裁切图片
     * $imgPath : 图片路径
     * $imgWidth : 裁剪宽度
     * $imgHeight : 裁剪高度
     * $returnType : 返回地址类型
     */
    public function ClipImage($imgPath = "", $imgWidth = 0, $imgHeight = 0,$returnType = "url") {
        if ($imgPath == "" || $imgPath == null) {
            return ZMImgClip::$imgErrorPath;
        }
        if ($imgWidth == 0 || $imgHeight == 0) {
            if (ZMImgClip::$imgWidth == 0 || ZMImgClip::$imgHeight == 0) {
                return $imgPath;
            } else {
                $imgWidth = ZMImgClip::$imgWidth;
                $imgHeight = ZMImgClip::$imgHeight;
            }
        }

        $file_name = $this->GetImgName($imgPath, $imgWidth, $imgHeight);
        $img_temp_path = $this->GetImgPath("", "folder");
        $file_path = $this->GetImgPath($file_name, "local");

        if (is_file($file_path)) {
            return $this->GetImgPath($file_name, $returnType);
        }
        $imgstream = file_get_contents($imgPath);
        if (!$imgstream) {
            return ZMImgClip::$imgErrorPath;
        }
        //读取图片
        $imgData = imagecreatefromstring($imgstream);
        $x = imagesx($imgData); //获取图片的宽
        $y = imagesy($imgData); //获取图片的高
        if ($x <= $imgWidth || $y <= $imgHeight) {
            imagedestroy($imgData);
            return $imgPath;
        }

        //计算缩放因子
        $Des_scale = $imgWidth / $imgHeight; //目标缩放
        $Origin_scale = $x / $y; //实际缩放
        if ($Origin_scale > $Des_scale) //与目标比例相比,原始尺寸的宽度较大
        {
            $thumbh = $y; //以100%高度为基准做裁切。
            $thumbw = $thumbh * $Des_scale; //高度为100%时对应的宽度
            $desCutPos_x = abs($thumbw - $x) / 2; //裁切位置
            $desCutPos_y = 0;            
        } else {
            $thumbw = $x;
            $thumbh = $thumbw / $Des_scale;
            $desCutPos_x = 0;
            $desCutPos_y = abs($thumbh - $y) / 2;
        }
        if (function_exists("imagecreatetruecolor")) {
            $desImgData = imagecreatetruecolor($imgWidth, $imgHeight);            
        } else {
            $desImgData = imagecreate($imgWidth, $imgHeight);
        }
        if (!imageCopyreSampled($desImgData, $imgData, 0, 0, $desCutPos_x, $desCutPos_y, $imgWidth, $imgHeight, $thumbw, $thumbh)) {
            imagedestroy($imgData);
            imagedestroy($desImgData);
            return $imgPath;
        }
        //保存
        if (!is_dir($img_temp_path)) {
            mkdir($img_temp_path, 0755, true);
        }

        if (!imagejpeg($desImgData, $file_path)) {
            imagedestroy($imgData);
            imagedestroy($desImgData);
            return $imgPath;
        }

        if(false){
            return $this->FileUpToQiNiu("","");
        }

        imagedestroy($imgData);
        imagedestroy($desImgData);

        return $this->GetImgPath($file_name,$returnType);
    }
}
?>

效果

下载我提供的文件并执行代码后,可以得到切割完成的图片,控制台输出图片地址。
imagecopyresampled

其应用可见mikusa博客首页的小缩略图。

我来吐槽

*

*

3位绅士参与评论

  1. littleplus09-01 01:05 回复

    getInstance,一看就知道是JAVA写多了,醒醒,这里是拍黄片。

    函数名不也应该和变量名一样,是第一个字小写的吗

    • 野兔09-01 06:14 回复

      大小写没规定,看个人习惯。有的人还喜欢把对象方法前边加个下划线呢。

  2. 熊猫小A08-31 10:16 回复

    我记得七牛云是可以帮你裁图像的,转换大小什么的也可以,难道是我记错了?
    不过 CDN 要钱的,还是自己搞经济实惠~

    • 野兔08-31 17:24 回复

      恩,七牛可以,不过我们的出发点不一样啦。本文思路默认本地服务器为最可靠的存储位置,由自己控制裁剪,可选上传七牛。当连接不到七牛时,本地依旧可以裁剪图像。

  3. mikusa08-30 23:30 回复

    梓喵的水文与我的水文(此处应有图片对比