image.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583
  1. <?php
  2. /*
  3. Copyright (c) 2009-2014 F3::Factory/Bong Cosca, All rights reserved.
  4. This file is part of the Fat-Free Framework (http://fatfree.sf.net).
  5. THE SOFTWARE AND DOCUMENTATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
  6. ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  7. IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
  8. PURPOSE.
  9. Please see the license.txt file for more information.
  10. */
  11. //! Image manipulation tools
  12. class Image {
  13. //@{ Messages
  14. const
  15. E_Color='Invalid color specified: %s',
  16. E_Font='CAPTCHA font not found',
  17. E_Length='Invalid CAPTCHA length: %s';
  18. //@}
  19. //@{ Positional cues
  20. const
  21. POS_Left=1,
  22. POS_Center=2,
  23. POS_Right=4,
  24. POS_Top=8,
  25. POS_Middle=16,
  26. POS_Bottom=32;
  27. //@}
  28. protected
  29. //! Source filename
  30. $file,
  31. //! Image resource
  32. $data,
  33. //! Enable/disable history
  34. $flag=FALSE,
  35. //! Filter count
  36. $count=0;
  37. /**
  38. * Convert RGB hex triad to array
  39. * @return array|FALSE
  40. * @param $color int
  41. **/
  42. function rgb($color) {
  43. $hex=str_pad($hex=dechex($color),$color<4096?3:6,'0',STR_PAD_LEFT);
  44. if (($len=strlen($hex))>6)
  45. user_error(sprintf(self::E_Color,'0x'.$hex));
  46. $color=str_split($hex,$len/3);
  47. foreach ($color as &$hue) {
  48. $hue=hexdec(str_repeat($hue,6/$len));
  49. unset($hue);
  50. }
  51. return $color;
  52. }
  53. /**
  54. * Invert image
  55. * @return object
  56. **/
  57. function invert() {
  58. imagefilter($this->data,IMG_FILTER_NEGATE);
  59. return $this->save();
  60. }
  61. /**
  62. * Adjust brightness (range:-255 to 255)
  63. * @return object
  64. * @param $level int
  65. **/
  66. function brightness($level) {
  67. imagefilter($this->data,IMG_FILTER_BRIGHTNESS,$level);
  68. return $this->save();
  69. }
  70. /**
  71. * Adjust contrast (range:-100 to 100)
  72. * @return object
  73. * @param $level int
  74. **/
  75. function contrast($level) {
  76. imagefilter($this->data,IMG_FILTER_CONTRAST,$level);
  77. return $this->save();
  78. }
  79. /**
  80. * Convert to grayscale
  81. * @return object
  82. **/
  83. function grayscale() {
  84. imagefilter($this->data,IMG_FILTER_GRAYSCALE);
  85. return $this->save();
  86. }
  87. /**
  88. * Adjust smoothness
  89. * @return object
  90. * @param $level int
  91. **/
  92. function smooth($level) {
  93. imagefilter($this->data,IMG_FILTER_SMOOTH,$level);
  94. return $this->save();
  95. }
  96. /**
  97. * Emboss the image
  98. * @return object
  99. **/
  100. function emboss() {
  101. imagefilter($this->data,IMG_FILTER_EMBOSS);
  102. return $this->save();
  103. }
  104. /**
  105. * Apply sepia effect
  106. * @return object
  107. **/
  108. function sepia() {
  109. imagefilter($this->data,IMG_FILTER_GRAYSCALE);
  110. imagefilter($this->data,IMG_FILTER_COLORIZE,90,60,45);
  111. return $this->save();
  112. }
  113. /**
  114. * Pixelate the image
  115. * @return object
  116. * @param $size int
  117. **/
  118. function pixelate($size) {
  119. imagefilter($this->data,IMG_FILTER_PIXELATE,$size,TRUE);
  120. return $this->save();
  121. }
  122. /**
  123. * Blur the image using Gaussian filter
  124. * @return object
  125. * @param $selective bool
  126. **/
  127. function blur($selective=FALSE) {
  128. imagefilter($this->data,
  129. $selective?IMG_FILTER_SELECTIVE_BLUR:IMG_FILTER_GAUSSIAN_BLUR);
  130. return $this->save();
  131. }
  132. /**
  133. * Apply sketch effect
  134. * @return object
  135. **/
  136. function sketch() {
  137. imagefilter($this->data,IMG_FILTER_MEAN_REMOVAL);
  138. return $this->save();
  139. }
  140. /**
  141. * Flip on horizontal axis
  142. * @return object
  143. **/
  144. function hflip() {
  145. $tmp=imagecreatetruecolor(
  146. $width=$this->width(),$height=$this->height());
  147. imagesavealpha($tmp,TRUE);
  148. imagefill($tmp,0,0,IMG_COLOR_TRANSPARENT);
  149. imagecopyresampled($tmp,$this->data,
  150. 0,0,$width-1,0,$width,$height,-$width,$height);
  151. imagedestroy($this->data);
  152. $this->data=$tmp;
  153. return $this->save();
  154. }
  155. /**
  156. * Flip on vertical axis
  157. * @return object
  158. **/
  159. function vflip() {
  160. $tmp=imagecreatetruecolor(
  161. $width=$this->width(),$height=$this->height());
  162. imagesavealpha($tmp,TRUE);
  163. imagefill($tmp,0,0,IMG_COLOR_TRANSPARENT);
  164. imagecopyresampled($tmp,$this->data,
  165. 0,0,0,$height-1,$width,$height,$width,-$height);
  166. imagedestroy($this->data);
  167. $this->data=$tmp;
  168. return $this->save();
  169. }
  170. /**
  171. * Crop the image
  172. * @return object
  173. * @param $x1 int
  174. * @param $y1 int
  175. * @param $x2 int
  176. * @param $y2 int
  177. **/
  178. function crop($x1,$y1,$x2,$y2) {
  179. $tmp=imagecreatetruecolor($width=$x2-$x1+1,$height=$y2-$y1+1);
  180. imagesavealpha($tmp,TRUE);
  181. imagefill($tmp,0,0,IMG_COLOR_TRANSPARENT);
  182. imagecopyresampled($tmp,$this->data,
  183. 0,0,$x1,$y1,$width,$height,$width,$height);
  184. imagedestroy($this->data);
  185. $this->data=$tmp;
  186. return $this->save();
  187. }
  188. /**
  189. * Resize image (Maintain aspect ratio); Crop relative to center
  190. * if flag is enabled; Enlargement allowed if flag is enabled
  191. * @return object
  192. * @param $width int
  193. * @param $height int
  194. * @param $crop bool
  195. * @param $enlarge bool
  196. **/
  197. function resize($width,$height,$crop=TRUE,$enlarge=TRUE) {
  198. // Adjust dimensions; retain aspect ratio
  199. $ratio=($origw=imagesx($this->data))/($origh=imagesy($this->data));
  200. if (!$crop)
  201. if ($width/$ratio<=$height)
  202. $height=$width/$ratio;
  203. else
  204. $width=$height*$ratio;
  205. if (!$enlarge) {
  206. $width=min($origw,$width);
  207. $height=min($origh,$height);
  208. }
  209. // Create blank image
  210. $tmp=imagecreatetruecolor($width,$height);
  211. imagesavealpha($tmp,TRUE);
  212. imagefill($tmp,0,0,IMG_COLOR_TRANSPARENT);
  213. // Resize
  214. if ($crop) {
  215. if ($width/$ratio<=$height) {
  216. $cropw=$origh*$width/$height;
  217. imagecopyresampled($tmp,$this->data,
  218. 0,0,($origw-$cropw)/2,0,$width,$height,$cropw,$origh);
  219. }
  220. else {
  221. $croph=$origw*$height/$width;
  222. imagecopyresampled($tmp,$this->data,
  223. 0,0,0,($origh-$croph)/2,$width,$height,$origw,$croph);
  224. }
  225. }
  226. else
  227. imagecopyresampled($tmp,$this->data,
  228. 0,0,0,0,$width,$height,$origw,$origh);
  229. imagedestroy($this->data);
  230. $this->data=$tmp;
  231. return $this->save();
  232. }
  233. /**
  234. * Rotate image
  235. * @return object
  236. * @param $angle int
  237. **/
  238. function rotate($angle) {
  239. $this->data=imagerotate($this->data,$angle,
  240. imagecolorallocatealpha($this->data,0,0,0,127));
  241. imagesavealpha($this->data,TRUE);
  242. return $this->save();
  243. }
  244. /**
  245. * Apply an image overlay
  246. * @return object
  247. * @param $img object
  248. * @param $align int|array
  249. * @param $alpha int
  250. **/
  251. function overlay(Image $img,$align=NULL,$alpha=100) {
  252. if (is_null($align))
  253. $align=self::POS_Right|self::POS_Bottom;
  254. if (is_array($align)) {
  255. list($posx,$posy)=$align;
  256. $align = 0;
  257. }
  258. $ovr=imagecreatefromstring($img->dump());
  259. imagesavealpha($ovr,TRUE);
  260. $imgw=$this->width();
  261. $imgh=$this->height();
  262. $ovrw=imagesx($ovr);
  263. $ovrh=imagesy($ovr);
  264. if ($align & self::POS_Left)
  265. $posx=0;
  266. if ($align & self::POS_Center)
  267. $posx=($imgw-$ovrw)/2;
  268. if ($align & self::POS_Right)
  269. $posx=$imgw-$ovrw;
  270. if ($align & self::POS_Top)
  271. $posy=0;
  272. if ($align & self::POS_Middle)
  273. $posy=($imgh-$ovrh)/2;
  274. if ($align & self::POS_Bottom)
  275. $posy=$imgh-$ovrh;
  276. if (empty($posx))
  277. $posx=0;
  278. if (empty($posy))
  279. $posy=0;
  280. if ($alpha==100)
  281. imagecopy($this->data,$ovr,$posx,$posy,0,0,$ovrw,$ovrh);
  282. else {
  283. $cut=imagecreatetruecolor($ovrw,$ovrh);
  284. imagecopy($cut,$this->data,0,0,$posx,$posy,$ovrw,$ovrh);
  285. imagecopy($cut,$ovr,0,0,0,0,$ovrw,$ovrh);
  286. imagecopymerge($this->data,$cut,$posx,$posy,0,0,$ovrw,$ovrh,$alpha);
  287. }
  288. return $this->save();
  289. }
  290. /**
  291. * Generate identicon
  292. * @return object
  293. * @param $str string
  294. * @param $size int
  295. * @param $blocks int
  296. **/
  297. function identicon($str,$size=64,$blocks=4) {
  298. $sprites=array(
  299. array(.5,1,1,0,1,1),
  300. array(.5,0,1,0,.5,1,0,1),
  301. array(.5,0,1,0,1,1,.5,1,1,.5),
  302. array(0,.5,.5,0,1,.5,.5,1,.5,.5),
  303. array(0,.5,1,0,1,1,0,1,1,.5),
  304. array(1,0,1,1,.5,1,1,.5,.5,.5),
  305. array(0,0,1,0,1,.5,0,0,.5,1,0,1),
  306. array(0,0,.5,0,1,.5,.5,1,0,1,.5,.5),
  307. array(.5,0,.5,.5,1,.5,1,1,.5,1,.5,.5,0,.5),
  308. array(0,0,1,0,.5,.5,1,.5,.5,1,.5,.5,0,1),
  309. array(0,.5,.5,1,1,.5,.5,0,1,0,1,1,0,1),
  310. array(.5,0,1,0,1,1,.5,1,1,.75,.5,.5,1,.25),
  311. array(0,.5,.5,0,.5,.5,1,0,1,.5,.5,1,.5,.5,0,1),
  312. array(0,0,1,0,1,1,0,1,1,.5,.5,.25,.5,.75,0,.5,.5,.25),
  313. array(0,.5,.5,.5,.5,0,1,0,.5,.5,1,.5,.5,1,.5,.5,0,1),
  314. array(0,0,1,0,.5,.5,.5,0,0,.5,1,.5,.5,1,.5,.5,0,1)
  315. );
  316. $hash=sha1($str);
  317. $this->data=imagecreatetruecolor($size,$size);
  318. list($r,$g,$b)=$this->rgb(hexdec(substr($hash,-3)));
  319. $fg=imagecolorallocate($this->data,$r,$g,$b);
  320. imagefill($this->data,0,0,IMG_COLOR_TRANSPARENT);
  321. $ctr=count($sprites);
  322. $dim=$blocks*floor($size/$blocks)*2/$blocks;
  323. for ($j=0,$y=ceil($blocks/2);$j<$y;$j++)
  324. for ($i=$j,$x=$blocks-1-$j;$i<$x;$i++) {
  325. $sprite=imagecreatetruecolor($dim,$dim);
  326. imagefill($sprite,0,0,IMG_COLOR_TRANSPARENT);
  327. if ($block=$sprites[
  328. hexdec($hash[($j*$blocks+$i)*2])%$ctr]) {
  329. for ($k=0,$pts=count($block);$k<$pts;$k++)
  330. $block[$k]*=$dim;
  331. imagefilledpolygon($sprite,$block,$pts/2,$fg);
  332. }
  333. $sprite=imagerotate($sprite,
  334. 90*(hexdec($hash[($j*$blocks+$i)*2+1])%4),
  335. imagecolorallocatealpha($sprite,0,0,0,127));
  336. for ($k=0;$k<4;$k++) {
  337. imagecopyresampled($this->data,$sprite,
  338. $i*$dim/2,$j*$dim/2,0,0,$dim/2,$dim/2,$dim,$dim);
  339. $this->data=imagerotate($this->data,90,
  340. imagecolorallocatealpha($this->data,0,0,0,127));
  341. }
  342. imagedestroy($sprite);
  343. }
  344. imagesavealpha($this->data,TRUE);
  345. return $this->save();
  346. }
  347. /**
  348. * Generate CAPTCHA image
  349. * @return object|FALSE
  350. * @param $font string
  351. * @param $size int
  352. * @param $len int
  353. * @param $key string
  354. * @param $path string
  355. * @param $fg int
  356. * @param $bg int
  357. **/
  358. function captcha($font,$size=24,$len=5,
  359. $key=NULL,$path='',$fg=0xFFFFFF,$bg=0x000000) {
  360. if ((!$ssl=extension_loaded('openssl')) && ($len<4 || $len>13)) {
  361. user_error(sprintf(self::E_Length,$len));
  362. return FALSE;
  363. }
  364. $fw=Base::instance();
  365. foreach ($fw->split($path?:$fw->get('UI').';./') as $dir)
  366. if (is_file($path=$dir.$font)) {
  367. $seed=strtoupper(substr(
  368. $ssl?bin2hex(openssl_random_pseudo_bytes($len)):uniqid(),
  369. -$len));
  370. $block=$size*3;
  371. $tmp=array();
  372. for ($i=0,$width=0,$height=0;$i<$len;$i++) {
  373. // Process at 2x magnification
  374. $box=imagettfbbox($size*2,0,$path,$seed[$i]);
  375. $w=$box[2]-$box[0];
  376. $h=$box[1]-$box[5];
  377. $char=imagecreatetruecolor($block,$block);
  378. imagefill($char,0,0,$bg);
  379. imagettftext($char,$size*2,0,
  380. ($block-$w)/2,$block-($block-$h)/2,
  381. $fg,$path,$seed[$i]);
  382. $char=imagerotate($char,mt_rand(-30,30),
  383. imagecolorallocatealpha($char,0,0,0,127));
  384. // Reduce to normal size
  385. $tmp[$i]=imagecreatetruecolor(
  386. ($w=imagesx($char))/2,($h=imagesy($char))/2);
  387. imagefill($tmp[$i],0,0,IMG_COLOR_TRANSPARENT);
  388. imagecopyresampled($tmp[$i],$char,0,0,0,0,$w/2,$h/2,$w,$h);
  389. imagedestroy($char);
  390. $width+=$i+1<$len?$block/2:$w/2;
  391. $height=max($height,$h/2);
  392. }
  393. $this->data=imagecreatetruecolor($width,$height);
  394. imagefill($this->data,0,0,IMG_COLOR_TRANSPARENT);
  395. for ($i=0;$i<$len;$i++) {
  396. imagecopy($this->data,$tmp[$i],
  397. $i*$block/2,($height-imagesy($tmp[$i]))/2,0,0,
  398. imagesx($tmp[$i]),imagesy($tmp[$i]));
  399. imagedestroy($tmp[$i]);
  400. }
  401. imagesavealpha($this->data,TRUE);
  402. if ($key)
  403. $fw->set($key,$seed);
  404. return $this->save();
  405. }
  406. user_error(self::E_Font);
  407. return FALSE;
  408. }
  409. /**
  410. * Return image width
  411. * @return int
  412. **/
  413. function width() {
  414. return imagesx($this->data);
  415. }
  416. /**
  417. * Return image height
  418. * @return int
  419. **/
  420. function height() {
  421. return imagesy($this->data);
  422. }
  423. /**
  424. * Send image to HTTP client
  425. * @return NULL
  426. **/
  427. function render() {
  428. $args=func_get_args();
  429. $format=$args?array_shift($args):'png';
  430. if (PHP_SAPI!='cli') {
  431. header('Content-Type: image/'.$format);
  432. header('X-Powered-By: '.Base::instance()->get('PACKAGE'));
  433. }
  434. call_user_func_array('image'.$format,
  435. array_merge(array($this->data),$args));
  436. }
  437. /**
  438. * Return image as a string
  439. * @return string
  440. **/
  441. function dump() {
  442. $args=func_get_args();
  443. $format=$args?array_shift($args):'png';
  444. ob_start();
  445. call_user_func_array('image'.$format,
  446. array_merge(array($this->data),$args));
  447. return ob_get_clean();
  448. }
  449. /**
  450. * Save current state
  451. * @return object
  452. **/
  453. function save() {
  454. $fw=Base::instance();
  455. if ($this->flag) {
  456. if (!is_dir($dir=$fw->get('TEMP')))
  457. mkdir($dir,Base::MODE,TRUE);
  458. $this->count++;
  459. $fw->write($dir.'/'.
  460. $fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'.
  461. $fw->hash($this->file).'-'.$this->count.'.png',
  462. $this->dump());
  463. }
  464. return $this;
  465. }
  466. /**
  467. * Revert to specified state
  468. * @return object
  469. * @param $state int
  470. **/
  471. function restore($state=1) {
  472. $fw=Base::instance();
  473. if ($this->flag && is_file($file=($path=$fw->get('TEMP').
  474. $fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'.
  475. $fw->hash($this->file).'-').$state.'.png')) {
  476. if (is_resource($this->data))
  477. imagedestroy($this->data);
  478. $this->data=imagecreatefromstring($fw->read($file));
  479. imagesavealpha($this->data,TRUE);
  480. foreach (glob($path.'*.png',GLOB_NOSORT) as $match)
  481. if (preg_match('/-(\d+)\.png/',$match,$parts) &&
  482. $parts[1]>$state)
  483. @unlink($match);
  484. $this->count=$state;
  485. }
  486. return $this;
  487. }
  488. /**
  489. * Undo most recently applied filter
  490. * @return object
  491. **/
  492. function undo() {
  493. if ($this->flag) {
  494. if ($this->count)
  495. $this->count--;
  496. return $this->restore($this->count);
  497. }
  498. return $this;
  499. }
  500. /**
  501. * Load string
  502. * @return object
  503. * @param $str string
  504. **/
  505. function load($str) {
  506. $this->data=imagecreatefromstring($str);
  507. imagesavealpha($this->data,TRUE);
  508. $this->save();
  509. return $this;
  510. }
  511. /**
  512. * Instantiate image
  513. * @param $file string
  514. * @param $flag bool
  515. * @param $path string
  516. **/
  517. function __construct($file=NULL,$flag=FALSE,$path='') {
  518. $this->flag=$flag;
  519. if ($file) {
  520. $fw=Base::instance();
  521. // Create image from file
  522. $this->file=$file;
  523. foreach ($fw->split($path?:$fw->get('UI').';./') as $dir)
  524. if (is_file($dir.$file))
  525. return $this->load($fw->read($dir.$file));
  526. }
  527. }
  528. /**
  529. * Wrap-up
  530. * @return NULL
  531. **/
  532. function __destruct() {
  533. if (is_resource($this->data)) {
  534. imagedestroy($this->data);
  535. $fw=Base::instance();
  536. $path=$fw->get('TEMP').
  537. $fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'.
  538. $fw->hash($this->file);
  539. if ($glob=@glob($path.'*.png',GLOB_NOSORT))
  540. foreach ($glob as $match)
  541. if (preg_match('/-(\d+)\.png/',$match))
  542. @unlink($match);
  543. }
  544. }
  545. }