php深入学习-如何生成微缩图

105 阅读9分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第30天,点击查看活动详情

获取图片微缩图有两种方法

  1. 生成微缩图,返回微缩图地址给src使用,这种方法一般直接在模板中调用 src="{:get_thum_images()}"
  2. 获取显示微缩图的地址,这个地址并不是微缩图的真是地址,而是生成程序的地址,此地址没访问就不会执行,因此在模板中使用不会阻塞执行,因为它仅仅是生成一个地址而已,浏览器渲染DOM时再请求src中的这个地址,此时才会进入到图片微缩图生成流程,当然也有缓存,如果有缓存直接输出已生成的图片

显然这个函数使用的是第一种方案,因此会出现生成微缩图时间过长,第二种方法不会,因为程序每次只生成一次微缩图,相当于分散了生成压力,第二种方法还有一个好处就是现在前端很多都是懒加载的,所以没必要生成模板时生成所有可能不需要的微缩图,所以一些情况下使用第二种方式会大大提高效率减少压力,现在很多大型汪涵也都是这么做的,不过第二种方案也有一个弊端,那就是性能问题,因为每次都需要将图片读到内存在输出到浏览器(第一种方案生成后不走php,直接走Nginx的静态服务了),很多储存平台都是使用专门的服务器做这项服务,如果单纯的使用php脚本承担这个任务的话可能有点压力。

探讨

区别
第一种方案和第二种方案的区别在于第一种方案是生成微缩图文件后再输出微缩图的真实地址,然后src获取图片流的时候走的是Nginx的静态服务;第二种方案是先得到能够输出图片流的地址给src,等真正请求微缩图时再请求此地址才生成微缩图,并直接将微缩图以图片流的形式输出,而不是输出图片地址,这种方案相当于是使用了中转/代理的机制,只要它们最终能够在src上输出图片流,图片就能正常显示。

图片流
src="?"要想显示图片,那么这个地址必须只想一个图片输出流,回到上面的问题,可以知道第一种方案图片流是由Nginx的静态服务提供的,第二种是微缩程序用php输出的,只要最终src得到它要的图片流,那么就能显示图片,而不管这个图片流是由谁提供的。

缓存
不管何种方式,为了效率,都使用了缓存,比如第一种方式第一次生成图片可能慢点,第二次生成相同规格的图片就会直接返回已经存在的图片地址;第二种方式还是同样的,如果发现存在已经生成过的微缩图,那么会直接读取输出,不会再次生成。

使用场景

  1. 直接在模板中调用
  2. 做接口,第一种方案返回的是微缩图地址字符串;第二种情况有两种方式做接口:(1)是直接输出图片流,(2)返回拼接的能够输出图片流的地址。不管怎么样这两种方案放到src上面都能够显示图片。

(关于性能下面会讨论)

性能
从生成微缩图程序本身来讲,两种方案性能是一样的,因为用的是一样的微缩图生成程序,但是使用场景的不同,就会有性能差异。

生成微缩图程序一般是模板中调用,由于第一种方式是直接生成,所以当量比较大(在模板中调用次数比较多),那么就会很慢(第一次慢,其后就有缓存了),而第二种方案,只是生成一个能够输出图片流的地址,或者说只是简单拼接出一个地址,并不会有生成微缩图的动作,所以比较快,然后这个地址返回给src,等到真正去加载这个图片时,才会访问这个能够输出图片流地址,此时才会真正生成微缩图,所以这种情况下第一种方案性能高一些。

从另一个角度来说,第一种方式在模板中使用,(在一个请求生命周期/php生命周期)被多次连续调用,就需要连续执行微缩图生成程序多次,所以慢,但是生成之后就会走Nginx的静态服务,效率就会高;而第二种方案,只是在真正访问图片时才会去请求能输出图片流的地址,一张图片一个请求,一个请求生成一个微缩图,也就是说将压力分散了,但是分散的每次请求都是php处理图片输出,性能没有Nginx高。一个集中的负载,一个多次的单一的请求,哪个队用户体验更好,哪个性能更高,哪个对服务器资源使用更合理,这个看实际情况了,没有最好的,只有最合适的,换句话说,最合适的就是最好的。

如果做接口的话,一个是生成微缩图再返回微缩图文件地址,一个是直接生成微缩图,直接输出,或者返回拼接的能够输出图片流的地址,将生成负载消耗留给下次的图片请求。

结论
经过详细分析两种方案各自的有点和缺点,所以,根据实际情况,考虑采用什么方案才是最好的。

  • 如果模板每次调用微缩图次数不多,那么采用第一种方案也是可以的,这样下次就会快了。
  • 如果第一种方案有时候太慢了,影响用户体验,并且你希望有更灵活的方式,那么可以采用第二种方案。
  • 而如果第二种方案会让你的服务器顶不住,或者感觉效率也并不高的话,那么就要考虑其他方法了,或者升级第二种方案的输出图片程序,像那些云储存平台一样,使用专门的服务器,更适合的语言去做这样的事,不断优化,找出影响性能的问题去解决,去突破瓶颈,还是一样,没有最好的设计,只有最合适的设计。

如果担心第一种方式有性能问题,第二种方式会增加服务器多的负担,不想让php输出静态文件,那么有一种同时兼顾二者的方案,第三个函数:判断如果当前图片微缩图存在就直接返回,不存在就返回调用第二种方案并返回。这样就同时兼顾两种方案了。


其他

iconfont.alicdn.com/t/147943496…

fuss10.elemecdn.com/b/d2/ba7e66…

dn-coding-net-production-static.qbox.me/fc0ccc44-a5…

比如这样的图片可能是专门的服务器做的

参考代码:

/**
 * 输出微缩图(获取微缩图输出地址)(性能相对高些,因为真实访问时毕竟是要输出图片流的)
 * @param  string  $src 图片url 从数据库直接读出来的 Uploads/Pic/2016-03-04/6sdfb345bds43f.png
 * @return int  $width 宽
 * @return int  $height 高
 * @return string  $format 格式
 * @return int  $type 类型
 * @return int  $q 质量
 * @return int  $s 是否生成缓存
 * @return int  $o 是否使用缓存
 * @return string  图片输出地址 Home/Pic/thumb/img/Uploads/Pic/2015-12-07/566523b7062dc.jpg/w/150/h/150/f/png/t/3/q/50/s/2/o/2
 */
function thumb($src, $width = 60, $height = 60, $format = 'jpg', $type = 2, $q = 80, $s = 1, $o = 1) {
    $array = array(
        'w'     => $width ? : 0,
        'h'     => $height ? : 0,
        'f'     => $format,
        't'     => $type,
        'q'     => $q,
        's'     => $s,
        'o'     => $o
    );
    if ('jpg' == $format) unset($array['f']);
    if (2 == $type)       unset($array['t']);
    if (80 == $q)          unset($array['q']);
    if (1 == $s)          unset($array['s']);
    if (1 == $o)          unset($array['o']);
    C('URL_CASE_INSENSITIVE', false);
    return U('Home/Pic/thumb/img/' . $src, $array, false);
}


/**
 * 获取微缩图地址src(性能相对低一些,输出的是真实图片地址)
 * @param  string  $src 图片url 从数据库直接读出来的 Uploads/Pic/2016-03-04/6sdfb345bds43f.png
 * @return int  $width 宽
 * @return int  $height 高
 * @return string  $format 格式
 * @return int  $type 类型
 * @return int  $q 质量
 * @return string  图片输出地址 /_thumb/Uploads/Pic/2016-03-21/56eee5d54bc61_20-20-jpg-2-80.jpg
 */
function get_thumb_src($src, $width = 60, $height = 60, $format = 'jpg', $type = 2, $q = 80) {
    $width = $width ? : 0;
    $height = $height ? : 0;
    return A('Home/Pic')->get_thumb_src($src, $width, $height, $format, $type, $q);
}

PicController.class.php

<?php
namespace Home\Controller;
use Core\Image;
/**
 * 图片控制器
 * Update time:2016-6-7 09:21:41
 */
class PicController {

    /**
     * 输出微缩图 home/pic/thumb/img/uploads/pic/2015-12-07/566523b7062dc.jpg/w/150/h/150/f/png/t/3/q/50/s/2/o/2
     * @param  string  $src      图像地址
     * @param  string $width   	 宽
     * @param  string $height    高
     * @param  string $format    输出格式 			默认jpg(貌似只有jpg支持清晰度调节)
     * @param  string $type    	 微缩方式 			默认2 缩放后填充类型
     * @param  string $q    	 清晰度调节 默认80
     * @param  string $s    	 是否缓存微缩图 	默认1缓存
     * @param  string $o    	 是否使用微缩图缓存 默认1可以使用
     * @return 图片流
     */
	public function thumb() {
		C('SHOW_PAGE_TRACE', false);

		if (!preg_match("#/img/(.*)/w/(\d+)/h/(\d+)#is", __SELF__, $matches)) return;

		preg_match("#/f/(\w+)#is", __SELF__, $a);

		preg_match("#/t/(\d+)#is", __SELF__, $b);

		preg_match("#/q/(\d+)#is", __SELF__, $c);

		preg_match("#/s/(\d+)#is", __SELF__, $d);

		preg_match("#/o/(\d+)#is", __SELF__, $e);

		$src 	= $matches[1];
		$width 	= $matches[2];
		$height = $matches[3];
		$format = isset($a[1]) ? $a[1] : 'jpg';
		$type 	= isset($b[1]) ? $b[1] : 2;
		$q 	 	= isset($c[1]) ? $c[1] : 80;
		$s 	 	= isset($d[1]) ? $d[1] : 1;
		$o 	 	= isset($e[1]) ? $e[1] : 1;

		$img = $this->getThumbFile($src, $width, $height, $format, $type, $q);

		if ($o && $this->isFile($src, $width, $height, $format, $type, $q)) {
 			$fileExt    = pathinfo($img, PATHINFO_EXTENSION);
            $mtime      = filemtime($img);
            $gmdate_mod = gmdate('D, d M Y H:i:s', $mtime) . ' GMT';
			header('Last-Modified: ' . $gmdate_mod);
 			header('Expires: ' . gmdate('D, d M Y H:i:s', time() + (60*60*24*30)) . ' GMT');
 			header('Content-type: image/' . $fileExt);
 			header('Content-Length: ' . filesize($img));
			readfile($img);
			return;
		}

		if (!file_exists(PATH . $src)) {
			return '';
		}

		$image = new Image();
		$image->open(PATH . $src);

		$width = $width == 0 ? $image->width() : $width;
		$height = $height == 0 ? $image->height() : $height;

		if ($s) {
			$dir = dirname($img);
	        if (!is_dir($dir)) {
	            mkdir($dir, 0777, true);
	        }
	        $image->thumb($width, $height, $type, $q)->save($img, $format, $q)->ss($format, $q);
		} else {
			$image->thumb($width, $height, $type, $q)->ss($format, $q);
		}
	}


	// 获取微缩图url
	public function get_thumb_src($src, $width, $height, $format, $type, $q) {
		if (!file_exists(PATH . $src)) {
			return '';
		}
		if(!$this->isFile($src, $width, $height, $format, $type, $q)) {
			$this->createThumb($src, $width, $height, $format, $type, $q);
		}
		return str_replace(PATH, __ROOT__ . '/', $this->getThumbFile($src, $width, $height, $format, $type, $q));
	}


	// 计算微缩图文件地址
	private function getThumbFile($src, $width, $height, $format, $type, $q) {
		$fileExt = pathinfo($src, PATHINFO_EXTENSION);
		$src = str_replace('.' . $fileExt, '', $src);
		return PATH . '_thumb/' . $src . '_' . $width . '-' . $height . '-' . $format . '-' . $type . '-' . $q . '.' . $fileExt;
	}


	// 创建微缩图
	private function createThumb($src, $width, $height, $format, $type, $q) {
		$img = $this->getThumbFile($src, $width, $height, $format, $type, $q);
		$image = new Image();
		$image->open(PATH . $src);
		$dir = dirname($img);
	        if (!is_dir($dir)) {
	            mkdir($dir, 0777, true);
        }
		$image->thumb($width, $height, $type)->save($img, $format, $q);
	}


	//  检测微缩图是否存在
	private function isFile($src, $width, $height, $format, $type, $q) {
		$img = $this->getThumbFile($src, $width, $height, $format, $type, $q);
		return file_exists($img) ? true : false;
	}
}

还有这个

function get_thum_images($src, $width, $height, $mode = 4)
{

    // 获取图片微缩图有两种方法
    // 1:生成微缩图,返回微缩图地址给src使用,这种方法一般直接在模板中调用 src="{:get_thum_images()}"
    // 2:获取显示微缩图的地址,这个地址并不是微缩图的真是地址,而是生成程序的地址,此地址没访问就不会执行,因此在模板中使用不会阻塞执行,因为它仅仅是生成一个地址而已,浏览器渲染DOM时再请求src中的这个地址,此时才会进入到图片微缩图生成流程,当然也有缓存,如果有缓存直接输出已生成的图片
    // 1,2区别在于1是生成微缩图在输出微缩图真实地址,2是得到微缩程序地址给src,等真正请求微缩图时在生成微缩图,并直接输出图片流,而不是输出图片地址
    // 显然这个函数使用的是第一种方案,因此会出现生成微缩图时间过长,第二种方法不会,因为图片每次只生成一次微缩图,相当于分散了生成压力,第二种方法还有一个好处就是现在前端很多都是懒加载的,所以没必要生成模板时生成所有可能不需要的微缩图,所以一些情况下使用第二种方式会大大提高效率减少压力,现在很多大型汪涵也都是这么做的,不过第二种方案也有一个弊端,那就是性能问题,因为每次都需要将图片读到内存在输出到浏览器(第一种方案生成后不走php,直接走Nginx的静态服务了),很多储存平台都是使用专门的服务器做这项服务,如果单纯的使用php脚本承担这个任务的话可能有点压力。
    set_time_limit(0);
    ini_set('memory_limit','-1');

    if (!file_exists('.' . $src)) {
        return '';
    }

    $fileExt = pathinfo($src, PATHINFO_EXTENSION);
    $src = str_replace('.' . $fileExt, '', $src);

    //判断缩略图是否存在
    $path             = "/Public/_thumb";
    $thumb = "{$src}_{$width}_{$height}_{$mode}.{$fileExt}";

    if (file_exists('.' . $path . $thumb)) {
        return $path . $thumb;
    }

    $image = new \Think\Image();
    $image->open('.' . $src . '.' . $fileExt);

    // 图像的路径必须要自己创建,否则保存失败
    $dir = dirname('.' . $path . $thumb);
    if (!is_dir($dir)) {
        mkdir($dir, 0777, true);
    }

    //参考文章 http://www.mb5u.com/biancheng/php/php_84533.html  改动参考 http://www.thinkphp.cn/topic/13542.html
    $a = $image->thumb($width, $height, $mode)->save('.' . $path . $thumb, null, 100); //按照原图的比例生成一个最大为$width*$height的缩略图并保存

    // 生成失败返回原图
    if (!file_exists('.' . $path . $thumb)) {
        return $src . '.' . $fileExt;
    }

    return $path . $thumb;
}