ImageImagick.class.php 19 KB

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