ImageGd.class.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | TOPThink [ WE CAN DO IT JUST THINK ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2010 http://topthink.com All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
  8. // +----------------------------------------------------------------------
  9. // | Author: 麦当苗儿 <zuojiazi.cn@gmail.com> <http://www.zjzit.cn>
  10. // +----------------------------------------------------------------------
  11. // | ImageGd.class.php 2013-03-05
  12. // +----------------------------------------------------------------------
  13. class ImageGd{
  14. /**
  15. * 图像资源对象
  16. * @var resource
  17. */
  18. private $img;
  19. /**
  20. * 图像信息,包括width,height,type,mime,size
  21. * @var array
  22. */
  23. private $info;
  24. /**
  25. * 构造方法,可用于打开一张图像
  26. * @param string $imgname 图像路径
  27. */
  28. public function __construct($imgname = null) {
  29. $imgname && $this->open($imgname);
  30. }
  31. /**
  32. * 打开一张图像
  33. * @param string $imgname 图像路径
  34. */
  35. public function open($imgname){
  36. //检测图像文件
  37. if(!is_file($imgname)) throw new Exception('不存在的图像文件');
  38. //获取图像信息
  39. $info = getimagesize($imgname);
  40. //检测图像合法性
  41. if(false === $info || (IMAGETYPE_GIF === $info[2] && empty($info['bits']))){
  42. throw new Exception('非法图像文件');
  43. }
  44. //设置图像信息
  45. $this->info = array(
  46. 'width' => $info[0],
  47. 'height' => $info[1],
  48. 'type' => image_type_to_extension($info[2], false),
  49. 'mime' => $info['mime'],
  50. );
  51. //销毁已存在的图像
  52. empty($this->img) || imagedestroy($this->img);
  53. //打开图像
  54. if('gif' == $this->info['type']){
  55. require_once 'GIF.class.php';
  56. $this->gif = new GIF($imgname);
  57. $this->img = imagecreatefromstring($this->gif->image());
  58. } else {
  59. $fun = "imagecreatefrom{$this->info['type']}";
  60. $this->img = $fun($imgname);
  61. }
  62. }
  63. /**
  64. * 保存图像
  65. * @param string $imgname 图像保存名称
  66. * @param string $type 图像类型
  67. * @param boolean $interlace 是否对JPEG类型图像设置隔行扫描
  68. */
  69. public function save($imgname, $type = null, $interlace = true){
  70. if(empty($this->img)) throw new Exception('没有可以被保存的图像资源');
  71. //自动获取图像类型
  72. if(is_null($type)){
  73. $type = $this->info['type'];
  74. } else {
  75. $type = strtolower($type);
  76. }
  77. //JPEG图像设置隔行扫描
  78. if('jpeg' == $type || 'jpg' == $type){
  79. $type = 'jpeg';
  80. imageinterlace($this->img, $interlace);
  81. }
  82. //保存图像
  83. if('gif' == $type && !empty($this->gif)){
  84. $this->gif->save($imgname);
  85. } else {
  86. $fun = "image{$type}";
  87. $fun($this->img, $imgname);
  88. }
  89. }
  90. /**
  91. * 返回图像宽度
  92. * @return integer 图像宽度
  93. */
  94. public function width(){
  95. if(empty($this->img)) throw new Exception('没有指定图像资源');
  96. return $this->info['width'];
  97. }
  98. /**
  99. * 返回图像高度
  100. * @return integer 图像高度
  101. */
  102. public function height(){
  103. if(empty($this->img)) throw new Exception('没有指定图像资源');
  104. return $this->info['height'];
  105. }
  106. /**
  107. * 返回图像类型
  108. * @return string 图像类型
  109. */
  110. public function type(){
  111. if(empty($this->img)) throw new Exception('没有指定图像资源');
  112. return $this->info['type'];
  113. }
  114. /**
  115. * 返回图像MIME类型
  116. * @return string 图像MIME类型
  117. */
  118. public function mime(){
  119. if(empty($this->img)) throw new Exception('没有指定图像资源');
  120. return $this->info['mime'];
  121. }
  122. /**
  123. * 返回图像尺寸数组 0 - 图像宽度,1 - 图像高度
  124. * @return array 图像尺寸
  125. */
  126. public function size(){
  127. if(empty($this->img)) throw new Exception('没有指定图像资源');
  128. return array($this->info['width'], $this->info['height']);
  129. }
  130. /**
  131. * 裁剪图像
  132. * @param integer $w 裁剪区域宽度
  133. * @param integer $h 裁剪区域高度
  134. * @param integer $x 裁剪区域x坐标
  135. * @param integer $y 裁剪区域y坐标
  136. * @param integer $width 图像保存宽度
  137. * @param integer $height 图像保存高度
  138. */
  139. public function crop($w, $h, $x = 0, $y = 0, $width = null, $height = null){
  140. if(empty($this->img)) throw new Exception('没有可以被裁剪的图像资源');
  141. //设置保存尺寸
  142. empty($width) && $width = $w;
  143. empty($height) && $height = $h;
  144. do {
  145. //创建新图像
  146. $img = imagecreatetruecolor($width, $height);
  147. // 调整默认颜色
  148. $color = imagecolorallocate($img, 255, 255, 255);
  149. imagefill($img, 0, 0, $color);
  150. //裁剪
  151. imagecopyresampled($img, $this->img, 0, 0, $x, $y, $width, $height, $w, $h);
  152. imagedestroy($this->img); //销毁原图
  153. //设置新图像
  154. $this->img = $img;
  155. } while(!empty($this->gif) && $this->gifNext());
  156. $this->info['width'] = $width;
  157. $this->info['height'] = $height;
  158. }
  159. /**
  160. * 生成缩略图
  161. * @param integer $width 缩略图最大宽度
  162. * @param integer $height 缩略图最大高度
  163. * @param integer $type 缩略图裁剪类型
  164. */
  165. public function thumb($width, $height, $type = THINKIMAGE_THUMB_SCALE){
  166. if(empty($this->img)) throw new Exception('没有可以被缩略的图像资源');
  167. //原图宽度和高度
  168. $w = $this->info['width'];
  169. $h = $this->info['height'];
  170. /* 计算缩略图生成的必要参数 */
  171. switch ($type) {
  172. /* 等比例缩放 */
  173. case THINKIMAGE_THUMB_SCALING:
  174. //原图尺寸小于缩略图尺寸则不进行缩略
  175. if($w < $width && $h < $height) return;
  176. //计算缩放比例
  177. $scale = min($width/$w, $height/$h);
  178. //设置缩略图的坐标及宽度和高度
  179. $x = $y = 0;
  180. $width = $w * $scale;
  181. $height = $h * $scale;
  182. break;
  183. /* 居中裁剪 */
  184. case THINKIMAGE_THUMB_CENTER:
  185. //计算缩放比例
  186. $scale = max($width/$w, $height/$h);
  187. //设置缩略图的坐标及宽度和高度
  188. $w = $width/$scale;
  189. $h = $height/$scale;
  190. $x = ($this->info['width'] - $w)/2;
  191. $y = ($this->info['height'] - $h)/2;
  192. break;
  193. /* 左上角裁剪 */
  194. case THINKIMAGE_THUMB_NORTHWEST:
  195. //计算缩放比例
  196. $scale = max($width/$w, $height/$h);
  197. //设置缩略图的坐标及宽度和高度
  198. $x = $y = 0;
  199. $w = $width/$scale;
  200. $h = $height/$scale;
  201. break;
  202. /* 右下角裁剪 */
  203. case THINKIMAGE_THUMB_SOUTHEAST:
  204. //计算缩放比例
  205. $scale = max($width/$w, $height/$h);
  206. //设置缩略图的坐标及宽度和高度
  207. $w = $width/$scale;
  208. $h = $height/$scale;
  209. $x = $this->info['width'] - $w;
  210. $y = $this->info['height'] - $h;
  211. break;
  212. /* 填充 */
  213. case THINKIMAGE_THUMB_FILLED:
  214. //计算缩放比例
  215. if($w < $width && $h < $height){
  216. $scale = 1;
  217. } else {
  218. $scale = min($width/$w, $height/$h);
  219. }
  220. //设置缩略图的坐标及宽度和高度
  221. $neww = $w * $scale;
  222. $newh = $h * $scale;
  223. $posx = ($width - $w * $scale)/2;
  224. $posy = ($height - $h * $scale)/2;
  225. do{
  226. //创建新图像
  227. $img = imagecreatetruecolor($width, $height);
  228. // 调整默认颜色
  229. $color = imagecolorallocate($img, 255, 255, 255);
  230. imagefill($img, 0, 0, $color);
  231. //裁剪
  232. imagecopyresampled($img, $this->img, $posx, $posy, $x, $y, $neww, $newh, $w, $h);
  233. imagedestroy($this->img); //销毁原图
  234. $this->img = $img;
  235. } while(!empty($this->gif) && $this->gifNext());
  236. $this->info['width'] = $width;
  237. $this->info['height'] = $height;
  238. return;
  239. /* 固定 */
  240. case THINKIMAGE_THUMB_FIXED:
  241. $x = $y = 0;
  242. break;
  243. default:
  244. throw new Exception('不支持的缩略图裁剪类型');
  245. }
  246. /* 裁剪图像 */
  247. $this->crop($w, $h, $x, $y, $width, $height);
  248. }
  249. /**
  250. * 添加水印
  251. * @param string $source 水印图片路径
  252. * @param integer $locate 水印位置
  253. * @param integer $alpha 水印透明度
  254. */
  255. public function water($source, $locate = THINKIMAGE_WATER_SOUTHEAST){
  256. //资源检测
  257. if(empty($this->img)) throw new Exception('没有可以被添加水印的图像资源');
  258. if(!is_file($source)) throw new Exception('水印图像不存在');
  259. //获取水印图像信息
  260. $info = getimagesize($source);
  261. if(false === $info || (IMAGETYPE_GIF === $info[2] && empty($info['bits']))){
  262. throw new Exception('非法水印文件');
  263. }
  264. //创建水印图像资源
  265. $fun = 'imagecreatefrom' . image_type_to_extension($info[2], false);
  266. $water = $fun($source);
  267. //设定水印图像的混色模式
  268. imagealphablending($water, true);
  269. /* 设定水印位置 */
  270. switch ($locate) {
  271. /* 右下角水印 */
  272. case THINKIMAGE_WATER_SOUTHEAST:
  273. $x = $this->info['width'] - $info[0];
  274. $y = $this->info['height'] - $info[1];
  275. break;
  276. /* 左下角水印 */
  277. case THINKIMAGE_WATER_SOUTHWEST:
  278. $x = 0;
  279. $y = $this->info['height'] - $info[1];
  280. break;
  281. /* 左上角水印 */
  282. case THINKIMAGE_WATER_NORTHWEST:
  283. $x = $y = 0;
  284. break;
  285. /* 右上角水印 */
  286. case THINKIMAGE_WATER_NORTHEAST:
  287. $x = $this->info['width'] - $info[0];
  288. $y = 0;
  289. break;
  290. /* 居中水印 */
  291. case THINKIMAGE_WATER_CENTER:
  292. $x = ($this->info['width'] - $info[0])/2;
  293. $y = ($this->info['height'] - $info[1])/2;
  294. break;
  295. /* 下居中水印 */
  296. case THINKIMAGE_WATER_SOUTH:
  297. $x = ($this->info['width'] - $info[0])/2;
  298. $y = $this->info['height'] - $info[1];
  299. break;
  300. /* 右居中水印 */
  301. case THINKIMAGE_WATER_EAST:
  302. $x = $this->info['width'] - $info[0];
  303. $y = ($this->info['height'] - $info[1])/2;
  304. break;
  305. /* 上居中水印 */
  306. case THINKIMAGE_WATER_NORTH:
  307. $x = ($this->info['width'] - $info[0])/2;
  308. $y = 0;
  309. break;
  310. /* 左居中水印 */
  311. case THINKIMAGE_WATER_WEST:
  312. $x = 0;
  313. $y = ($this->info['height'] - $info[1])/2;
  314. break;
  315. default:
  316. /* 自定义水印坐标 */
  317. if(is_array($locate)){
  318. list($x, $y) = $locate;
  319. } else {
  320. throw new Exception('不支持的水印位置类型');
  321. }
  322. }
  323. do{
  324. //添加水印
  325. $src = imagecreatetruecolor($info[0], $info[1]);
  326. // 调整默认颜色
  327. $color = imagecolorallocate($src, 255, 255, 255);
  328. imagefill($src, 0, 0, $color);
  329. imagecopy($src, $this->img, 0, 0, $x, $y, $info[0], $info[1]);
  330. imagecopy($src, $water, 0, 0, 0, 0, $info[0], $info[1]);
  331. imagecopymerge($this->img, $src, $x, $y, 0, 0, $info[0], $info[1], 100);
  332. //销毁零时图片资源
  333. imagedestroy($src);
  334. } while(!empty($this->gif) && $this->gifNext());
  335. //销毁水印资源
  336. imagedestroy($water);
  337. }
  338. /**
  339. * 图像添加文字
  340. * @param string $text 添加的文字
  341. * @param string $font 字体路径
  342. * @param integer $size 字号
  343. * @param string $color 文字颜色
  344. * @param integer $locate 文字写入位置
  345. * @param integer $offset 文字相对当前位置的偏移量
  346. * @param integer $angle 文字倾斜角度
  347. */
  348. public function text($text, $font, $size, $color = '#00000000',
  349. $locate = THINKIMAGE_WATER_SOUTHEAST, $offset = 0, $angle = 0){
  350. //资源检测
  351. if(empty($this->img)) throw new Exception('没有可以被写入文字的图像资源');
  352. if(!is_file($font)) throw new Exception("不存在的字体文件:{$font}");
  353. //获取文字信息
  354. $info = imagettfbbox($size, $angle, $font, $text);
  355. $minx = min($info[0], $info[2], $info[4], $info[6]);
  356. $maxx = max($info[0], $info[2], $info[4], $info[6]);
  357. $miny = min($info[1], $info[3], $info[5], $info[7]);
  358. $maxy = max($info[1], $info[3], $info[5], $info[7]);
  359. /* 计算文字初始坐标和尺寸 */
  360. $x = $minx;
  361. $y = abs($miny);
  362. $w = $maxx - $minx;
  363. $h = $maxy - $miny;
  364. /* 设定文字位置 */
  365. switch ($locate) {
  366. /* 右下角文字 */
  367. case THINKIMAGE_WATER_SOUTHEAST:
  368. $x += $this->info['width'] - $w;
  369. $y += $this->info['height'] - $h;
  370. break;
  371. /* 左下角文字 */
  372. case THINKIMAGE_WATER_SOUTHWEST:
  373. $y += $this->info['height'] - $h;
  374. break;
  375. /* 左上角文字 */
  376. case THINKIMAGE_WATER_NORTHWEST:
  377. // 起始坐标即为左上角坐标,无需调整
  378. break;
  379. /* 右上角文字 */
  380. case THINKIMAGE_WATER_NORTHEAST:
  381. $x += $this->info['width'] - $w;
  382. break;
  383. /* 居中文字 */
  384. case THINKIMAGE_WATER_CENTER:
  385. $x += ($this->info['width'] - $w)/2;
  386. $y += ($this->info['height'] - $h)/2;
  387. break;
  388. /* 下居中文字 */
  389. case THINKIMAGE_WATER_SOUTH:
  390. $x += ($this->info['width'] - $w)/2;
  391. $y += $this->info['height'] - $h;
  392. break;
  393. /* 右居中文字 */
  394. case THINKIMAGE_WATER_EAST:
  395. $x += $this->info['width'] - $w;
  396. $y += ($this->info['height'] - $h)/2;
  397. break;
  398. /* 上居中文字 */
  399. case THINKIMAGE_WATER_NORTH:
  400. $x += ($this->info['width'] - $w)/2;
  401. break;
  402. /* 左居中文字 */
  403. case THINKIMAGE_WATER_WEST:
  404. $y += ($this->info['height'] - $h)/2;
  405. break;
  406. default:
  407. /* 自定义文字坐标 */
  408. if(is_array($locate)){
  409. list($posx, $posy) = $locate;
  410. $x += $posx;
  411. $y += $posy;
  412. } else {
  413. throw new Exception('不支持的文字位置类型');
  414. }
  415. }
  416. /* 设置偏移量 */
  417. if(is_array($offset)){
  418. $offset = array_map('intval', $offset);
  419. list($ox, $oy) = $offset;
  420. } else{
  421. $offset = intval($offset);
  422. $ox = $oy = $offset;
  423. }
  424. /* 设置颜色 */
  425. if(is_string($color) && 0 === strpos($color, '#')){
  426. $color = str_split(substr($color, 1), 2);
  427. $color = array_map('hexdec', $color);
  428. if(empty($color[3]) || $color[3] > 127){
  429. $color[3] = 0;
  430. }
  431. } elseif (!is_array($color)) {
  432. throw new Exception('错误的颜色值');
  433. }
  434. do{
  435. /* 写入文字 */
  436. $col = imagecolorallocatealpha($this->img, $color[0], $color[1], $color[2], $color[3]);
  437. imagettftext($this->img, $size, $angle, $x + $ox, $y + $oy, $col, $font, $text);
  438. } while(!empty($this->gif) && $this->gifNext());
  439. }
  440. /* 切换到GIF的下一帧并保存当前帧,内部使用 */
  441. private function gifNext(){
  442. ob_start();
  443. ob_implicit_flush(0);
  444. imagegif($this->img);
  445. $img = ob_get_clean();
  446. $this->gif->image($img);
  447. $next = $this->gif->nextImage();
  448. if($next){
  449. $this->img = imagecreatefromstring($next);
  450. return $next;
  451. } else {
  452. $this->img = imagecreatefromstring($this->gif->image());
  453. return false;
  454. }
  455. }
  456. /**
  457. * 析构方法,用于销毁图像资源
  458. */
  459. public function __destruct() {
  460. empty($this->img) || imagedestroy($this->img);
  461. }
  462. }