Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715
  1. <?php
  2. //namespace Gumlet;
  3. /**
  4. * PHP class to resize and scale images
  5. */
  6. class ImageResize
  7. {
  8. const CROPTOP = 1;
  9. const CROPCENTRE = 2;
  10. const CROPCENTER = 2;
  11. const CROPBOTTOM = 3;
  12. const CROPLEFT = 4;
  13. const CROPRIGHT = 5;
  14. const CROPTOPCENTER = 6;
  15. const IMG_FLIP_HORIZONTAL = 0;
  16. const IMG_FLIP_VERTICAL = 1;
  17. const IMG_FLIP_BOTH = 2;
  18. public $quality_jpg = 85;
  19. public $quality_webp = 85;
  20. public $quality_png = 6;
  21. public $quality_truecolor = true;
  22. public $interlace = 1;
  23. public $source_type;
  24. protected $source_image;
  25. protected $original_w;
  26. protected $original_h;
  27. protected $dest_x = 0;
  28. protected $dest_y = 0;
  29. protected $source_x;
  30. protected $source_y;
  31. protected $dest_w;
  32. protected $dest_h;
  33. protected $source_w;
  34. protected $source_h;
  35. protected $source_info;
  36. protected $filters = [];
  37. /**
  38. * Create instance from a strng
  39. *
  40. * @param string $image_data
  41. * @return ImageResize
  42. * @throws ImageResizeException
  43. */
  44. public static function createFromString($image_data)
  45. {
  46. if (empty($image_data) || $image_data === null) {
  47. throw new ImageResizeException('image_data must not be empty');
  48. }
  49. $resize = new self('data://application/octet-stream;base64,' . base64_encode($image_data));
  50. return $resize;
  51. }
  52. /**
  53. * Add filter function for use right before save image to file.
  54. *
  55. * @param callable $filter
  56. * @return $this
  57. */
  58. public function addFilter(callable $filter)
  59. {
  60. $this->filters[] = $filter;
  61. return $this;
  62. }
  63. /**
  64. * Apply filters.
  65. *
  66. * @param $image resource an image resource identifier
  67. * @param $filterType filter type and default value is IMG_FILTER_NEGATE
  68. */
  69. protected function applyFilter($image, $filterType = IMG_FILTER_NEGATE)
  70. {
  71. foreach ($this->filters as $function) {
  72. $function($image, $filterType);
  73. }
  74. }
  75. /**
  76. * Loads image source and its properties to the instanciated object
  77. *
  78. * @param string $filename
  79. * @return ImageResize
  80. * @throws ImageResizeException
  81. */
  82. public function __construct($filename)
  83. {
  84. if (!defined('IMAGETYPE_WEBP')) {
  85. define('IMAGETYPE_WEBP', 18);
  86. }
  87. if ($filename === null || empty($filename) || (substr($filename, 0, 7) !== 'data://' && !is_file($filename))) {
  88. throw new ImageResizeException('File does not exist');
  89. }
  90. $finfo = finfo_open(FILEINFO_MIME_TYPE);
  91. if (strstr(finfo_file($finfo, $filename), 'image') === false) {
  92. throw new ImageResizeException('Unsupported file type');
  93. }
  94. if (!$image_info = getimagesize($filename, $this->source_info)) {
  95. $image_info = getimagesize($filename);
  96. }
  97. if (!$image_info) {
  98. throw new ImageResizeException('Could not read file');
  99. }
  100. list(
  101. $this->original_w,
  102. $this->original_h,
  103. $this->source_type
  104. ) = $image_info;
  105. switch ($this->source_type) {
  106. case IMAGETYPE_GIF:
  107. $this->source_image = imagecreatefromgif($filename);
  108. break;
  109. case IMAGETYPE_JPEG:
  110. $this->source_image = $this->imageCreateJpegfromExif($filename);
  111. // set new width and height for image, maybe it has changed
  112. $this->original_w = ImageSX($this->source_image);
  113. $this->original_h = ImageSY($this->source_image);
  114. break;
  115. case IMAGETYPE_PNG:
  116. $this->source_image = imagecreatefrompng($filename);
  117. break;
  118. case IMAGETYPE_WEBP:
  119. if (version_compare(PHP_VERSION, '5.5.0', '<')) {
  120. throw new ImageResizeException('For WebP support PHP >= 5.5.0 is required');
  121. }
  122. $this->source_image = imagecreatefromwebp($filename);
  123. break;
  124. default:
  125. throw new ImageResizeException('Unsupported image type');
  126. }
  127. if (!$this->source_image) {
  128. throw new ImageResizeException('Could not load image');
  129. }
  130. return $this->resize($this->getSourceWidth(), $this->getSourceHeight());
  131. }
  132. // http://stackoverflow.com/a/28819866
  133. public function imageCreateJpegfromExif($filename)
  134. {
  135. $img = imagecreatefromjpeg($filename);
  136. if (!function_exists('exif_read_data') || !isset($this->source_info['APP1']) || strpos($this->source_info['APP1'], 'Exif') !== 0) {
  137. return $img;
  138. }
  139. $exif = @exif_read_data($filename);
  140. if (!$exif || !isset($exif['Orientation'])) {
  141. return $img;
  142. }
  143. $orientation = $exif['Orientation'];
  144. if ($orientation === 6 || $orientation === 5) {
  145. $img = imagerotate($img, 270, null);
  146. } elseif ($orientation === 3 || $orientation === 4) {
  147. $img = imagerotate($img, 180, null);
  148. } elseif ($orientation === 8 || $orientation === 7) {
  149. $img = imagerotate($img, 90, null);
  150. }
  151. if ($orientation === 5 || $orientation === 4 || $orientation === 7) {
  152. if(function_exists('imageflip')) {
  153. imageflip($img, IMG_FLIP_HORIZONTAL);
  154. } else {
  155. $this->imageFlip($img, IMG_FLIP_HORIZONTAL);
  156. }
  157. }
  158. return $img;
  159. }
  160. /**
  161. * Saves new image
  162. *
  163. * @param string $filename
  164. * @param string $image_type
  165. * @param integer $quality
  166. * @param integer $permissions
  167. * @return \static
  168. */
  169. public function save($filename, $image_type = null, $quality = null, $permissions = null)
  170. {
  171. $image_type = $image_type ?: $this->source_type;
  172. $quality = is_numeric($quality) ? (int) abs($quality) : null;
  173. switch ($image_type) {
  174. case IMAGETYPE_GIF:
  175. $dest_image = imagecreatetruecolor($this->getDestWidth(), $this->getDestHeight());
  176. $background = imagecolorallocatealpha($dest_image, 255, 255, 255, 1);
  177. imagecolortransparent($dest_image, $background);
  178. imagefill($dest_image, 0, 0, $background);
  179. imagesavealpha($dest_image, true);
  180. break;
  181. case IMAGETYPE_JPEG:
  182. $dest_image = imagecreatetruecolor($this->getDestWidth(), $this->getDestHeight());
  183. $background = imagecolorallocate($dest_image, 255, 255, 255);
  184. imagefilledrectangle($dest_image, 0, 0, $this->getDestWidth(), $this->getDestHeight(), $background);
  185. break;
  186. case IMAGETYPE_WEBP:
  187. if (version_compare(PHP_VERSION, '5.5.0', '<')) {
  188. throw new ImageResizeException('For WebP support PHP >= 5.5.0 is required');
  189. }
  190. $dest_image = imagecreatetruecolor($this->getDestWidth(), $this->getDestHeight());
  191. $background = imagecolorallocate($dest_image, 255, 255, 255);
  192. imagefilledrectangle($dest_image, 0, 0, $this->getDestWidth(), $this->getDestHeight(), $background);
  193. break;
  194. case IMAGETYPE_PNG:
  195. if (!$this->quality_truecolor && !imageistruecolor($this->source_image)) {
  196. $dest_image = imagecreate($this->getDestWidth(), $this->getDestHeight());
  197. $background = imagecolorallocatealpha($dest_image, 255, 255, 255, 1);
  198. imagecolortransparent($dest_image, $background);
  199. imagefill($dest_image, 0, 0, $background);
  200. } else {
  201. $dest_image = imagecreatetruecolor($this->getDestWidth(), $this->getDestHeight());
  202. }
  203. imagealphablending($dest_image, false);
  204. imagesavealpha($dest_image, true);
  205. break;
  206. }
  207. imageinterlace($dest_image, $this->interlace);
  208. imagecopyresampled(
  209. $dest_image,
  210. $this->source_image,
  211. $this->dest_x,
  212. $this->dest_y,
  213. $this->source_x,
  214. $this->source_y,
  215. $this->getDestWidth(),
  216. $this->getDestHeight(),
  217. $this->source_w,
  218. $this->source_h
  219. );
  220. $this->applyFilter($dest_image);
  221. switch ($image_type) {
  222. case IMAGETYPE_GIF:
  223. imagegif($dest_image, $filename);
  224. break;
  225. case IMAGETYPE_JPEG:
  226. if ($quality === null || $quality > 100) {
  227. $quality = $this->quality_jpg;
  228. }
  229. imagejpeg($dest_image, $filename, $quality);
  230. break;
  231. case IMAGETYPE_WEBP:
  232. if (version_compare(PHP_VERSION, '5.5.0', '<')) {
  233. throw new ImageResizeException('For WebP support PHP >= 5.5.0 is required');
  234. }
  235. if ($quality === null) {
  236. $quality = $this->quality_webp;
  237. }
  238. imagewebp($dest_image, $filename, $quality);
  239. break;
  240. case IMAGETYPE_PNG:
  241. if ($quality === null || $quality > 9) {
  242. $quality = $this->quality_png;
  243. }
  244. imagepng($dest_image, $filename, $quality);
  245. break;
  246. }
  247. if ($permissions) {
  248. chmod($filename, $permissions);
  249. }
  250. imagedestroy($dest_image);
  251. return $this;
  252. }
  253. /**
  254. * Convert the image to string
  255. *
  256. * @param int $image_type
  257. * @param int $quality
  258. * @return string
  259. */
  260. public function getImageAsString($image_type = null, $quality = null)
  261. {
  262. $string_temp = tempnam(sys_get_temp_dir(), '');
  263. $this->save($string_temp, $image_type, $quality);
  264. $string = file_get_contents($string_temp);
  265. unlink($string_temp);
  266. return $string;
  267. }
  268. /**
  269. * Convert the image to string with the current settings
  270. *
  271. * @return string
  272. */
  273. public function __toString()
  274. {
  275. return $this->getImageAsString();
  276. }
  277. /**
  278. * Outputs image to browser
  279. * @param string $image_type
  280. * @param integer $quality
  281. */
  282. public function output($image_type = null, $quality = null)
  283. {
  284. $image_type = $image_type ?: $this->source_type;
  285. header('Content-Type: ' . image_type_to_mime_type($image_type));
  286. $this->save(null, $image_type, $quality);
  287. }
  288. /**
  289. * Resizes image according to the given short side (short side proportional)
  290. *
  291. * @param integer $max_short
  292. * @param boolean $allow_enlarge
  293. * @return \static
  294. */
  295. public function resizeToShortSide($max_short, $allow_enlarge = false)
  296. {
  297. if ($this->getSourceHeight() < $this->getSourceWidth()) {
  298. $ratio = $max_short / $this->getSourceHeight();
  299. $long = $this->getSourceWidth() * $ratio;
  300. $this->resize($long, $max_short, $allow_enlarge);
  301. } else {
  302. $ratio = $max_short / $this->getSourceWidth();
  303. $long = $this->getSourceHeight() * $ratio;
  304. $this->resize($max_short, $long, $allow_enlarge);
  305. }
  306. return $this;
  307. }
  308. /**
  309. * Resizes image according to the given long side (short side proportional)
  310. *
  311. * @param integer $max_long
  312. * @param boolean $allow_enlarge
  313. * @return \static
  314. */
  315. public function resizeToLongSide($max_long, $allow_enlarge = false)
  316. {
  317. if ($this->getSourceHeight() > $this->getSourceWidth()) {
  318. $ratio = $max_long / $this->getSourceHeight();
  319. $short = $this->getSourceWidth() * $ratio;
  320. $this->resize($short, $max_long, $allow_enlarge);
  321. } else {
  322. $ratio = $max_long / $this->getSourceWidth();
  323. $short = $this->getSourceHeight() * $ratio;
  324. $this->resize($max_long, $short, $allow_enlarge);
  325. }
  326. return $this;
  327. }
  328. /**
  329. * Resizes image according to the given height (width proportional)
  330. *
  331. * @param integer $height
  332. * @param boolean $allow_enlarge
  333. * @return \static
  334. */
  335. public function resizeToHeight($height, $allow_enlarge = false)
  336. {
  337. $ratio = $height / $this->getSourceHeight();
  338. $width = $this->getSourceWidth() * $ratio;
  339. $this->resize($width, $height, $allow_enlarge);
  340. return $this;
  341. }
  342. /**
  343. * Resizes image according to the given width (height proportional)
  344. *
  345. * @param integer $width
  346. * @param boolean $allow_enlarge
  347. * @return \static
  348. */
  349. public function resizeToWidth($width, $allow_enlarge = false)
  350. {
  351. $ratio = $width / $this->getSourceWidth();
  352. $height = $this->getSourceHeight() * $ratio;
  353. $this->resize($width, $height, $allow_enlarge);
  354. return $this;
  355. }
  356. /**
  357. * Resizes image to best fit inside the given dimensions
  358. *
  359. * @param integer $max_width
  360. * @param integer $max_height
  361. * @param boolean $allow_enlarge
  362. * @return \static
  363. */
  364. public function resizeToBestFit($max_width, $max_height, $allow_enlarge = false)
  365. {
  366. if ($this->getSourceWidth() <= $max_width && $this->getSourceHeight() <= $max_height && $allow_enlarge === false) {
  367. return $this;
  368. }
  369. $ratio = $this->getSourceHeight() / $this->getSourceWidth();
  370. $width = $max_width;
  371. $height = $width * $ratio;
  372. if ($height > $max_height) {
  373. $height = $max_height;
  374. $width = $height / $ratio;
  375. }
  376. return $this->resize($width, $height, $allow_enlarge);
  377. }
  378. /**
  379. * Resizes image according to given scale (proportionally)
  380. *
  381. * @param integer|float $scale
  382. * @return \static
  383. */
  384. public function scale($scale)
  385. {
  386. $width = $this->getSourceWidth() * $scale / 100;
  387. $height = $this->getSourceHeight() * $scale / 100;
  388. $this->resize($width, $height, true);
  389. return $this;
  390. }
  391. /**
  392. * Resizes image according to the given width and height
  393. *
  394. * @param integer $width
  395. * @param integer $height
  396. * @param boolean $allow_enlarge
  397. * @return \static
  398. */
  399. public function resize($width, $height, $allow_enlarge = false)
  400. {
  401. if (!$allow_enlarge) {
  402. // if the user hasn't explicitly allowed enlarging,
  403. // but either of the dimensions are larger then the original,
  404. // then just use original dimensions - this logic may need rethinking
  405. if ($width > $this->getSourceWidth() || $height > $this->getSourceHeight()) {
  406. $width = $this->getSourceWidth();
  407. $height = $this->getSourceHeight();
  408. }
  409. }
  410. $this->source_x = 0;
  411. $this->source_y = 0;
  412. $this->dest_w = $width;
  413. $this->dest_h = $height;
  414. $this->source_w = $this->getSourceWidth();
  415. $this->source_h = $this->getSourceHeight();
  416. return $this;
  417. }
  418. /**
  419. * Crops image according to the given width, height and crop position
  420. *
  421. * @param integer $width
  422. * @param integer $height
  423. * @param boolean $allow_enlarge
  424. * @param integer $position
  425. * @return \static
  426. */
  427. public function crop($width, $height, $allow_enlarge = false, $position = self::CROPCENTER)
  428. {
  429. if (!$allow_enlarge) {
  430. // this logic is slightly different to resize(),
  431. // it will only reset dimensions to the original
  432. // if that particular dimenstion is larger
  433. if ($width > $this->getSourceWidth()) {
  434. $width = $this->getSourceWidth();
  435. }
  436. if ($height > $this->getSourceHeight()) {
  437. $height = $this->getSourceHeight();
  438. }
  439. }
  440. $ratio_source = $this->getSourceWidth() / $this->getSourceHeight();
  441. $ratio_dest = $width / $height;
  442. if ($ratio_dest < $ratio_source) {
  443. $this->resizeToHeight($height, $allow_enlarge);
  444. $excess_width = ($this->getDestWidth() - $width) / $this->getDestWidth() * $this->getSourceWidth();
  445. $this->source_w = $this->getSourceWidth() - $excess_width;
  446. $this->source_x = $this->getCropPosition($excess_width, $position);
  447. $this->dest_w = $width;
  448. } else {
  449. $this->resizeToWidth($width, $allow_enlarge);
  450. $excess_height = ($this->getDestHeight() - $height) / $this->getDestHeight() * $this->getSourceHeight();
  451. $this->source_h = $this->getSourceHeight() - $excess_height;
  452. $this->source_y = $this->getCropPosition($excess_height, $position);
  453. $this->dest_h = $height;
  454. }
  455. return $this;
  456. }
  457. /**
  458. * Crops image according to the given width, height, x and y
  459. *
  460. * @param integer $width
  461. * @param integer $height
  462. * @param integer $x
  463. * @param integer $y
  464. * @return \static
  465. */
  466. public function freecrop($width, $height, $x = false, $y = false)
  467. {
  468. if ($x === false || $y === false) {
  469. return $this->crop($width, $height);
  470. }
  471. $this->source_x = $x;
  472. $this->source_y = $y;
  473. if ($width > $this->getSourceWidth() - $x) {
  474. $this->source_w = $this->getSourceWidth() - $x;
  475. } else {
  476. $this->source_w = $width;
  477. }
  478. if ($height > $this->getSourceHeight() - $y) {
  479. $this->source_h = $this->getSourceHeight() - $y;
  480. } else {
  481. $this->source_h = $height;
  482. }
  483. $this->dest_w = $width;
  484. $this->dest_h = $height;
  485. return $this;
  486. }
  487. /**
  488. * Gets source width
  489. *
  490. * @return integer
  491. */
  492. public function getSourceWidth()
  493. {
  494. return $this->original_w;
  495. }
  496. /**
  497. * Gets source height
  498. *
  499. * @return integer
  500. */
  501. public function getSourceHeight()
  502. {
  503. return $this->original_h;
  504. }
  505. /**
  506. * Gets width of the destination image
  507. *
  508. * @return integer
  509. */
  510. public function getDestWidth()
  511. {
  512. return $this->dest_w;
  513. }
  514. /**
  515. * Gets height of the destination image
  516. * @return integer
  517. */
  518. public function getDestHeight()
  519. {
  520. return $this->dest_h;
  521. }
  522. /**
  523. * Gets crop position (X or Y) according to the given position
  524. *
  525. * @param integer $expectedSize
  526. * @param integer $position
  527. * @return integer
  528. */
  529. protected function getCropPosition($expectedSize, $position = self::CROPCENTER)
  530. {
  531. $size = 0;
  532. switch ($position) {
  533. case self::CROPBOTTOM:
  534. case self::CROPRIGHT:
  535. $size = $expectedSize;
  536. break;
  537. case self::CROPCENTER:
  538. case self::CROPCENTRE:
  539. $size = $expectedSize / 2;
  540. break;
  541. case self::CROPTOPCENTER:
  542. $size = $expectedSize / 4;
  543. break;
  544. }
  545. return $size;
  546. }
  547. /**
  548. * Flips an image using a given mode if PHP version is lower than 5.5
  549. *
  550. * @param resource $image
  551. * @param integer $mode
  552. * @return null
  553. */
  554. public function imageFlip($image, $mode)
  555. {
  556. switch($mode) {
  557. case self::IMG_FLIP_HORIZONTAL: {
  558. $max_x = imagesx($image) - 1;
  559. $half_x = $max_x / 2;
  560. $sy = imagesy($image);
  561. $temp_image = imageistruecolor($image)? imagecreatetruecolor(1, $sy): imagecreate(1, $sy);
  562. for ($x = 0; $x < $half_x; ++$x) {
  563. imagecopy($temp_image, $image, 0, 0, $x, 0, 1, $sy);
  564. imagecopy($image, $image, $x, 0, $max_x - $x, 0, 1, $sy);
  565. imagecopy($image, $temp_image, $max_x - $x, 0, 0, 0, 1, $sy);
  566. }
  567. break;
  568. }
  569. case self::IMG_FLIP_VERTICAL: {
  570. $sx = imagesx($image);
  571. $max_y = imagesy($image) - 1;
  572. $half_y = $max_y / 2;
  573. $temp_image = imageistruecolor($image)? imagecreatetruecolor($sx, 1): imagecreate($sx, 1);
  574. for ($y = 0; $y < $half_y; ++$y) {
  575. imagecopy($temp_image, $image, 0, 0, 0, $y, $sx, 1);
  576. imagecopy($image, $image, 0, $y, 0, $max_y - $y, $sx, 1);
  577. imagecopy($image, $temp_image, 0, $max_y - $y, 0, 0, $sx, 1);
  578. }
  579. break;
  580. }
  581. case self::IMG_FLIP_BOTH: {
  582. $sx = imagesx($image);
  583. $sy = imagesy($image);
  584. $temp_image = imagerotate($image, 180, 0);
  585. imagecopy($image, $temp_image, 0, 0, 0, 0, $sx, $sy);
  586. break;
  587. }
  588. default:
  589. return null;
  590. }
  591. imagedestroy($temp_image);
  592. }
  593. }