Imagick.class.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  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@vip.qq.com> <https://www.nanodreamtech.com>
  10. // +----------------------------------------------------------------------
  11. // | ImageImagick.class.php 2013-03-06
  12. // +----------------------------------------------------------------------
  13. namespace Think\Image\Driver;
  14. use Think\Image;
  15. class Imagick{
  16. /**
  17. * 图像资源对象
  18. * @var resource
  19. */
  20. private $img;
  21. /**
  22. * 图像信息,包括width,height,type,mime,size
  23. * @var array
  24. */
  25. private $info;
  26. /**
  27. * 构造方法,可用于打开一张图像
  28. * @param string $imgname 图像路径
  29. */
  30. public function __construct($imgname = null) {
  31. $imgname && $this->open($imgname);
  32. }
  33. /**
  34. * 打开一张图像
  35. * @param string $imgname 图像路径
  36. */
  37. public function open($imgname){
  38. //检测图像文件
  39. if(!is_file($imgname)) E('不存在的图像文件');
  40. //销毁已存在的图像
  41. empty($this->img) || $this->img->destroy();
  42. //载入图像
  43. $this->img = new \Imagick(realpath($imgname));
  44. //设置图像信息
  45. $this->info = array(
  46. 'width' => $this->img->getImageWidth(),
  47. 'height' => $this->img->getImageHeight(),
  48. 'type' => strtolower($this->img->getImageFormat()),
  49. 'mime' => $this->img->getImageMimeType(),
  50. );
  51. }
  52. /**
  53. * 保存图像
  54. * @param string $imgname 图像保存名称
  55. * @param string $type 图像类型
  56. * @param integer $quality JPEG图像质量
  57. * @param boolean $interlace 是否对JPEG类型图像设置隔行扫描
  58. */
  59. public function save($imgname, $type = null, $quality=80,$interlace = true){
  60. if(empty($this->img)) E('没有可以被保存的图像资源');
  61. //设置图片类型
  62. if(is_null($type)){
  63. $type = $this->info['type'];
  64. } else {
  65. $type = strtolower($type);
  66. $this->img->setImageFormat($type);
  67. }
  68. //JPEG图像设置隔行扫描
  69. if('jpeg' == $type || 'jpg' == $type){
  70. $this->img->setImageInterlaceScheme(1);
  71. }
  72. // 设置图像质量
  73. $this->img->setImageCompressionQuality($quality);
  74. //去除图像配置信息
  75. $this->img->stripImage();
  76. //保存图像
  77. $imgname = realpath(dirname($imgname)) . '/' . basename($imgname); //强制绝对路径
  78. if ('gif' == $type) {
  79. $this->img->writeImages($imgname, true);
  80. } else {
  81. $this->img->writeImage($imgname);
  82. }
  83. }
  84. /**
  85. * 返回图像宽度
  86. * @return integer 图像宽度
  87. */
  88. public function width(){
  89. if(empty($this->img)) E('没有指定图像资源');
  90. return $this->info['width'];
  91. }
  92. /**
  93. * 返回图像高度
  94. * @return integer 图像高度
  95. */
  96. public function height(){
  97. if(empty($this->img)) E('没有指定图像资源');
  98. return $this->info['height'];
  99. }
  100. /**
  101. * 返回图像类型
  102. * @return string 图像类型
  103. */
  104. public function type(){
  105. if(empty($this->img)) E('没有指定图像资源');
  106. return $this->info['type'];
  107. }
  108. /**
  109. * 返回图像MIME类型
  110. * @return string 图像MIME类型
  111. */
  112. public function mime(){
  113. if(empty($this->img)) E('没有指定图像资源');
  114. return $this->info['mime'];
  115. }
  116. /**
  117. * 返回图像尺寸数组 0 - 图像宽度,1 - 图像高度
  118. * @return array 图像尺寸
  119. */
  120. public function size(){
  121. if(empty($this->img)) E('没有指定图像资源');
  122. return array($this->info['width'], $this->info['height']);
  123. }
  124. /**
  125. * 裁剪图像
  126. * @param integer $w 裁剪区域宽度
  127. * @param integer $h 裁剪区域高度
  128. * @param integer $x 裁剪区域x坐标
  129. * @param integer $y 裁剪区域y坐标
  130. * @param integer $width 图像保存宽度
  131. * @param integer $height 图像保存高度
  132. */
  133. public function crop($w, $h, $x = 0, $y = 0, $width = null, $height = null){
  134. if(empty($this->img)) E('没有可以被裁剪的图像资源');
  135. //设置保存尺寸
  136. empty($width) && $width = $w;
  137. empty($height) && $height = $h;
  138. //裁剪图片
  139. if('gif' == $this->info['type']){
  140. $img = $this->img->coalesceImages();
  141. $this->img->destroy(); //销毁原图
  142. //循环裁剪每一帧
  143. do {
  144. $this->_crop($w, $h, $x, $y, $width, $height, $img);
  145. } while ($img->nextImage());
  146. //压缩图片
  147. $this->img = $img->deconstructImages();
  148. $img->destroy(); //销毁零时图片
  149. } else {
  150. $this->_crop($w, $h, $x, $y, $width, $height);
  151. }
  152. }
  153. /* 裁剪图片,内部调用 */
  154. private function _crop($w, $h, $x, $y, $width, $height, $img = null){
  155. is_null($img) && $img = $this->img;
  156. //裁剪
  157. $info = $this->info;
  158. if($x != 0 || $y != 0 || $w != $info['width'] || $h != $info['height']){
  159. $img->cropImage($w, $h, $x, $y);
  160. $img->setImagePage($w, $h, 0, 0); //调整画布和图片一致
  161. }
  162. //调整大小
  163. if($w != $width || $h != $height){
  164. $img->sampleImage($width, $height);
  165. }
  166. //设置缓存尺寸
  167. $this->info['width'] = $w;
  168. $this->info['height'] = $h;
  169. }
  170. /**
  171. * 生成缩略图
  172. * @param integer $width 缩略图最大宽度
  173. * @param integer $height 缩略图最大高度
  174. * @param integer $type 缩略图裁剪类型
  175. */
  176. public function thumb($width, $height, $type = Image::IMAGE_THUMB_SCALE){
  177. if(empty($this->img)) E('没有可以被缩略的图像资源');
  178. //原图宽度和高度
  179. $w = $this->info['width'];
  180. $h = $this->info['height'];
  181. /* 计算缩略图生成的必要参数 */
  182. switch ($type) {
  183. /* 等比例缩放 */
  184. case Image::IMAGE_THUMB_SCALE:
  185. //原图尺寸小于缩略图尺寸则不进行缩略
  186. if($w < $width && $h < $height) return;
  187. //计算缩放比例
  188. $scale = min($width/$w, $height/$h);
  189. //设置缩略图的坐标及宽度和高度
  190. $x = $y = 0;
  191. $width = $w * $scale;
  192. $height = $h * $scale;
  193. break;
  194. /* 居中裁剪 */
  195. case Image::IMAGE_THUMB_CENTER:
  196. //计算缩放比例
  197. $scale = max($width/$w, $height/$h);
  198. //设置缩略图的坐标及宽度和高度
  199. $w = $width/$scale;
  200. $h = $height/$scale;
  201. $x = ($this->info['width'] - $w)/2;
  202. $y = ($this->info['height'] - $h)/2;
  203. break;
  204. /* 左上角裁剪 */
  205. case Image::IMAGE_THUMB_NORTHWEST:
  206. //计算缩放比例
  207. $scale = max($width/$w, $height/$h);
  208. //设置缩略图的坐标及宽度和高度
  209. $x = $y = 0;
  210. $w = $width/$scale;
  211. $h = $height/$scale;
  212. break;
  213. /* 右下角裁剪 */
  214. case Image::IMAGE_THUMB_SOUTHEAST:
  215. //计算缩放比例
  216. $scale = max($width/$w, $height/$h);
  217. //设置缩略图的坐标及宽度和高度
  218. $w = $width/$scale;
  219. $h = $height/$scale;
  220. $x = $this->info['width'] - $w;
  221. $y = $this->info['height'] - $h;
  222. break;
  223. /* 填充 */
  224. case Image::IMAGE_THUMB_FILLED:
  225. //计算缩放比例
  226. if($w < $width && $h < $height){
  227. $scale = 1;
  228. } else {
  229. $scale = min($width/$w, $height/$h);
  230. }
  231. //设置缩略图的坐标及宽度和高度
  232. $neww = $w * $scale;
  233. $newh = $h * $scale;
  234. $posx = ($width - $w * $scale)/2;
  235. $posy = ($height - $h * $scale)/2;
  236. //创建一张新图像
  237. $newimg = new \Imagick();
  238. $newimg->newImage($width, $height, 'white', $this->info['type']);
  239. if('gif' == $this->info['type']){
  240. $imgs = $this->img->coalesceImages();
  241. $img = new \Imagick();
  242. $this->img->destroy(); //销毁原图
  243. //循环填充每一帧
  244. do {
  245. //填充图像
  246. $image = $this->_fill($newimg, $posx, $posy, $neww, $newh, $imgs);
  247. $img->addImage($image);
  248. $img->setImageDelay($imgs->getImageDelay());
  249. $img->setImagePage($width, $height, 0, 0);
  250. $image->destroy(); //销毁零时图片
  251. } while ($imgs->nextImage());
  252. //压缩图片
  253. $this->img->destroy();
  254. $this->img = $img->deconstructImages();
  255. $imgs->destroy(); //销毁零时图片
  256. $img->destroy(); //销毁零时图片
  257. } else {
  258. //填充图像
  259. $img = $this->_fill($newimg, $posx, $posy, $neww, $newh);
  260. //销毁原图
  261. $this->img->destroy();
  262. $this->img = $img;
  263. }
  264. //设置新图像属性
  265. $this->info['width'] = $width;
  266. $this->info['height'] = $height;
  267. return;
  268. /* 固定 */
  269. case Image::IMAGE_THUMB_FIXED:
  270. $x = $y = 0;
  271. break;
  272. default:
  273. E('不支持的缩略图裁剪类型');
  274. }
  275. /* 裁剪图像 */
  276. $this->crop($w, $h, $x, $y, $width, $height);
  277. }
  278. /* 填充指定图像,内部使用 */
  279. private function _fill($newimg, $posx, $posy, $neww, $newh, $img = null){
  280. is_null($img) && $img = $this->img;
  281. /* 将指定图片绘入空白图片 */
  282. $draw = new \ImagickDraw();
  283. $draw->composite($img->getImageCompose(), $posx, $posy, $neww, $newh, $img);
  284. $image = $newimg->clone();
  285. $image->drawImage($draw);
  286. $draw->destroy();
  287. return $image;
  288. }
  289. /**
  290. * 添加水印
  291. * @param string $source 水印图片路径
  292. * @param integer $locate 水印位置
  293. * @param integer $alpha 水印透明度
  294. */
  295. public function water($source, $locate = Image::IMAGE_WATER_SOUTHEAST,$alpha=80){
  296. //资源检测
  297. if(empty($this->img)) E('没有可以被添加水印的图像资源');
  298. if(!is_file($source)) E('水印图像不存在');
  299. //创建水印图像资源
  300. $water = new \Imagick(realpath($source));
  301. $info = array($water->getImageWidth(), $water->getImageHeight());
  302. /* 设定水印位置 */
  303. switch ($locate) {
  304. /* 右下角水印 */
  305. case Image::IMAGE_WATER_SOUTHEAST:
  306. $x = $this->info['width'] - $info[0];
  307. $y = $this->info['height'] - $info[1];
  308. break;
  309. /* 左下角水印 */
  310. case Image::IMAGE_WATER_SOUTHWEST:
  311. $x = 0;
  312. $y = $this->info['height'] - $info[1];
  313. break;
  314. /* 左上角水印 */
  315. case Image::IMAGE_WATER_NORTHWEST:
  316. $x = $y = 0;
  317. break;
  318. /* 右上角水印 */
  319. case Image::IMAGE_WATER_NORTHEAST:
  320. $x = $this->info['width'] - $info[0];
  321. $y = 0;
  322. break;
  323. /* 居中水印 */
  324. case Image::IMAGE_WATER_CENTER:
  325. $x = ($this->info['width'] - $info[0])/2;
  326. $y = ($this->info['height'] - $info[1])/2;
  327. break;
  328. /* 下居中水印 */
  329. case Image::IMAGE_WATER_SOUTH:
  330. $x = ($this->info['width'] - $info[0])/2;
  331. $y = $this->info['height'] - $info[1];
  332. break;
  333. /* 右居中水印 */
  334. case Image::IMAGE_WATER_EAST:
  335. $x = $this->info['width'] - $info[0];
  336. $y = ($this->info['height'] - $info[1])/2;
  337. break;
  338. /* 上居中水印 */
  339. case Image::IMAGE_WATER_NORTH:
  340. $x = ($this->info['width'] - $info[0])/2;
  341. $y = 0;
  342. break;
  343. /* 左居中水印 */
  344. case Image::IMAGE_WATER_WEST:
  345. $x = 0;
  346. $y = ($this->info['height'] - $info[1])/2;
  347. break;
  348. default:
  349. /* 自定义水印坐标 */
  350. if(is_array($locate)){
  351. list($x, $y) = $locate;
  352. } else {
  353. E('不支持的水印位置类型');
  354. }
  355. }
  356. //创建绘图资源
  357. $draw = new \ImagickDraw();
  358. $draw->composite($water->getImageCompose(), $x, $y, $info[0], $info[1], $water);
  359. if('gif' == $this->info['type']){
  360. $img = $this->img->coalesceImages();
  361. $this->img->destroy(); //销毁原图
  362. do{
  363. //添加水印
  364. $img->drawImage($draw);
  365. } while ($img->nextImage());
  366. //压缩图片
  367. $this->img = $img->deconstructImages();
  368. $img->destroy(); //销毁零时图片
  369. } else {
  370. //添加水印
  371. $this->img->drawImage($draw);
  372. }
  373. //销毁水印资源
  374. $draw->destroy();
  375. $water->destroy();
  376. }
  377. /**
  378. * 图像添加文字
  379. * @param string $text 添加的文字
  380. * @param string $font 字体路径
  381. * @param integer $size 字号
  382. * @param string $color 文字颜色
  383. * @param integer $locate 文字写入位置
  384. * @param integer $offset 文字相对当前位置的偏移量
  385. * @param integer $angle 文字倾斜角度
  386. */
  387. public function text($text, $font, $size, $color = '#00000000',
  388. $locate = Image::IMAGE_WATER_SOUTHEAST, $offset = 0, $angle = 0){
  389. //资源检测
  390. if(empty($this->img)) E('没有可以被写入文字的图像资源');
  391. if(!is_file($font)) E("不存在的字体文件:{$font}");
  392. //获取颜色和透明度
  393. if(is_array($color)){
  394. $color = array_map('dechex', $color);
  395. foreach ($color as &$value) {
  396. $value = str_pad($value, 2, '0', STR_PAD_LEFT);
  397. }
  398. $color = '#' . implode('', $color);
  399. } elseif(!is_string($color) || 0 !== strpos($color, '#')) {
  400. E('错误的颜色值');
  401. }
  402. $col = substr($color, 0, 7);
  403. $alp = strlen($color) == 9 ? substr($color, -2) : 0;
  404. //获取文字信息
  405. $draw = new \ImagickDraw();
  406. $draw->setFont(realpath($font));
  407. $draw->setFontSize($size);
  408. $draw->setFillColor($col);
  409. $draw->setFillAlpha(1-hexdec($alp)/127);
  410. $draw->setTextAntialias(true);
  411. $draw->setStrokeAntialias(true);
  412. $metrics = $this->img->queryFontMetrics($draw, $text);
  413. /* 计算文字初始坐标和尺寸 */
  414. $x = 0;
  415. $y = $metrics['ascender'];
  416. $w = $metrics['textWidth'];
  417. $h = $metrics['textHeight'];
  418. /* 设定文字位置 */
  419. switch ($locate) {
  420. /* 右下角文字 */
  421. case Image::IMAGE_WATER_SOUTHEAST:
  422. $x += $this->info['width'] - $w;
  423. $y += $this->info['height'] - $h;
  424. break;
  425. /* 左下角文字 */
  426. case Image::IMAGE_WATER_SOUTHWEST:
  427. $y += $this->info['height'] - $h;
  428. break;
  429. /* 左上角文字 */
  430. case Image::IMAGE_WATER_NORTHWEST:
  431. // 起始坐标即为左上角坐标,无需调整
  432. break;
  433. /* 右上角文字 */
  434. case Image::IMAGE_WATER_NORTHEAST:
  435. $x += $this->info['width'] - $w;
  436. break;
  437. /* 居中文字 */
  438. case Image::IMAGE_WATER_CENTER:
  439. $x += ($this->info['width'] - $w)/2;
  440. $y += ($this->info['height'] - $h)/2;
  441. break;
  442. /* 下居中文字 */
  443. case Image::IMAGE_WATER_SOUTH:
  444. $x += ($this->info['width'] - $w)/2;
  445. $y += $this->info['height'] - $h;
  446. break;
  447. /* 右居中文字 */
  448. case Image::IMAGE_WATER_EAST:
  449. $x += $this->info['width'] - $w;
  450. $y += ($this->info['height'] - $h)/2;
  451. break;
  452. /* 上居中文字 */
  453. case Image::IMAGE_WATER_NORTH:
  454. $x += ($this->info['width'] - $w)/2;
  455. break;
  456. /* 左居中文字 */
  457. case Image::IMAGE_WATER_WEST:
  458. $y += ($this->info['height'] - $h)/2;
  459. break;
  460. default:
  461. /* 自定义文字坐标 */
  462. if(is_array($locate)){
  463. list($posx, $posy) = $locate;
  464. $x += $posx;
  465. $y += $posy;
  466. } else {
  467. E('不支持的文字位置类型');
  468. }
  469. }
  470. /* 设置偏移量 */
  471. if(is_array($offset)){
  472. $offset = array_map('intval', $offset);
  473. list($ox, $oy) = $offset;
  474. } else{
  475. $offset = intval($offset);
  476. $ox = $oy = $offset;
  477. }
  478. /* 写入文字 */
  479. if('gif' == $this->info['type']){
  480. $img = $this->img->coalesceImages();
  481. $this->img->destroy(); //销毁原图
  482. do{
  483. $img->annotateImage($draw, $x + $ox, $y + $oy, $angle, $text);
  484. } while ($img->nextImage());
  485. //压缩图片
  486. $this->img = $img->deconstructImages();
  487. $img->destroy(); //销毁零时图片
  488. } else {
  489. $this->img->annotateImage($draw, $x + $ox, $y + $oy, $angle, $text);
  490. }
  491. $draw->destroy();
  492. }
  493. /**
  494. * 析构方法,用于销毁图像资源
  495. */
  496. public function __destruct() {
  497. empty($this->img) || $this->img->destroy();
  498. }
  499. }