PageRenderTime 48ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/image.php

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