Gd2.php 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Framework\Image\Adapter;
  7. /**
  8. * Gd2 adapter.
  9. *
  10. * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
  11. */
  12. class Gd2 extends \Magento\Framework\Image\Adapter\AbstractAdapter
  13. {
  14. /**
  15. * Required extensions
  16. *
  17. * @var array
  18. */
  19. protected $_requiredExtensions = ["gd"];
  20. /**
  21. * Image output callbacks by type
  22. *
  23. * @var array
  24. */
  25. private static $_callbacks = [
  26. IMAGETYPE_GIF => ['output' => 'imagegif', 'create' => 'imagecreatefromgif'],
  27. IMAGETYPE_JPEG => ['output' => 'imagejpeg', 'create' => 'imagecreatefromjpeg'],
  28. IMAGETYPE_PNG => ['output' => 'imagepng', 'create' => 'imagecreatefrompng'],
  29. IMAGETYPE_XBM => ['output' => 'imagexbm', 'create' => 'imagecreatefromxbm'],
  30. IMAGETYPE_WBMP => ['output' => 'imagewbmp', 'create' => 'imagecreatefromxbm'],
  31. ];
  32. /**
  33. * Whether image was resized or not
  34. *
  35. * @var bool
  36. */
  37. protected $_resized = false;
  38. /**
  39. * For properties reset, e.g. mimeType caching.
  40. *
  41. * @return void
  42. */
  43. protected function _reset()
  44. {
  45. $this->_fileMimeType = null;
  46. $this->_fileType = null;
  47. }
  48. /**
  49. * Open image for processing
  50. *
  51. * @param string $filename
  52. * @return void
  53. * @throws \OverflowException
  54. */
  55. public function open($filename)
  56. {
  57. $this->_fileName = $filename;
  58. $this->_reset();
  59. $this->getMimeType();
  60. $this->_getFileAttributes();
  61. if ($this->_isMemoryLimitReached()) {
  62. throw new \OverflowException('Memory limit has been reached.');
  63. }
  64. $this->imageDestroy();
  65. $this->_imageHandler = call_user_func(
  66. $this->_getCallback('create', null, sprintf('Unsupported image format. File: %s', $this->_fileName)),
  67. $this->_fileName
  68. );
  69. $fileType = $this->getImageType();
  70. if (in_array($fileType, [IMAGETYPE_PNG, IMAGETYPE_GIF])) {
  71. $this->_keepTransparency = true;
  72. if ($this->_imageHandler) {
  73. $isAlpha = $this->checkAlpha($this->_fileName);
  74. if ($isAlpha) {
  75. $this->_fillBackgroundColor($this->_imageHandler);
  76. }
  77. }
  78. }
  79. }
  80. /**
  81. * Checks whether memory limit is reached.
  82. *
  83. * @return bool
  84. */
  85. protected function _isMemoryLimitReached()
  86. {
  87. $limit = $this->_convertToByte(ini_get('memory_limit'));
  88. $requiredMemory = $this->_getImageNeedMemorySize($this->_fileName);
  89. if ($limit === -1) {
  90. // A limit of -1 means no limit: http://www.php.net/manual/en/ini.core.php#ini.memory-limit
  91. return false;
  92. }
  93. return memory_get_usage(true) + $requiredMemory > $limit;
  94. }
  95. /**
  96. * Get image needed memory size
  97. *
  98. * @param string $file
  99. * @return float|int
  100. */
  101. protected function _getImageNeedMemorySize($file)
  102. {
  103. $imageInfo = getimagesize($file);
  104. if (!isset($imageInfo[0]) || !isset($imageInfo[1])) {
  105. return 0;
  106. }
  107. if (!isset($imageInfo['channels'])) {
  108. // if there is no info about this parameter lets set it for maximum
  109. $imageInfo['channels'] = 4;
  110. }
  111. if (!isset($imageInfo['bits'])) {
  112. // if there is no info about this parameter lets set it for maximum
  113. $imageInfo['bits'] = 8;
  114. }
  115. return round(
  116. ($imageInfo[0] * $imageInfo[1] * $imageInfo['bits'] * $imageInfo['channels'] / 8 + pow(2, 16)) * 1.65
  117. );
  118. }
  119. /**
  120. * Converts memory value (e.g. 64M, 129K) to bytes.
  121. *
  122. * Case insensitive value might be used.
  123. *
  124. * @param string $memoryValue
  125. * @return int
  126. */
  127. protected function _convertToByte($memoryValue)
  128. {
  129. if (stripos($memoryValue, 'G') !== false) {
  130. return (int)$memoryValue * pow(1024, 3);
  131. } elseif (stripos($memoryValue, 'M') !== false) {
  132. return (int)$memoryValue * 1024 * 1024;
  133. } elseif (stripos($memoryValue, 'K') !== false) {
  134. return (int)$memoryValue * 1024;
  135. }
  136. return (int)$memoryValue;
  137. }
  138. /**
  139. * Save image to specific path.
  140. *
  141. * If some folders of path does not exist they will be created
  142. *
  143. * @param null|string $destination
  144. * @param null|string $newName
  145. * @return void
  146. * @throws \Exception If destination path is not writable
  147. */
  148. public function save($destination = null, $newName = null)
  149. {
  150. $fileName = $this->_prepareDestination($destination, $newName);
  151. if (!$this->_resized) {
  152. // keep alpha transparency
  153. $isAlpha = false;
  154. $isTrueColor = false;
  155. $this->_getTransparency($this->_imageHandler, $this->_fileType, $isAlpha, $isTrueColor);
  156. if ($isAlpha) {
  157. if ($isTrueColor) {
  158. $newImage = imagecreatetruecolor($this->_imageSrcWidth, $this->_imageSrcHeight);
  159. } else {
  160. $newImage = imagecreate($this->_imageSrcWidth, $this->_imageSrcHeight);
  161. }
  162. $this->_fillBackgroundColor($newImage);
  163. imagecopy($newImage, $this->_imageHandler, 0, 0, 0, 0, $this->_imageSrcWidth, $this->_imageSrcHeight);
  164. $this->imageDestroy();
  165. $this->_imageHandler = $newImage;
  166. }
  167. }
  168. // Enable interlace
  169. imageinterlace($this->_imageHandler, true);
  170. // Set image quality value
  171. switch ($this->_fileType) {
  172. case IMAGETYPE_PNG:
  173. $quality = 9; // For PNG files compression level must be from 0 (no compression) to 9.
  174. break;
  175. case IMAGETYPE_JPEG:
  176. $quality = $this->quality();
  177. break;
  178. default:
  179. $quality = null; // No compression.
  180. }
  181. // Prepare callback method parameters
  182. $functionParameters = [$this->_imageHandler, $fileName];
  183. if ($quality) {
  184. $functionParameters[] = $quality;
  185. }
  186. call_user_func_array($this->_getCallback('output'), $functionParameters);
  187. }
  188. /**
  189. * Render image and return its binary contents.
  190. *
  191. * @see \Magento\Framework\Image\Adapter\AbstractAdapter::getImage
  192. *
  193. * @return string
  194. */
  195. public function getImage()
  196. {
  197. ob_start();
  198. call_user_func($this->_getCallback('output'), $this->_imageHandler);
  199. return ob_get_clean();
  200. }
  201. /**
  202. * Obtain function name, basing on image type and callback type
  203. *
  204. * @param string $callbackType
  205. * @param null|int $fileType
  206. * @param string $unsupportedText
  207. * @return string
  208. * @throws \Exception
  209. */
  210. private function _getCallback($callbackType, $fileType = null, $unsupportedText = 'Unsupported image format.')
  211. {
  212. if (null === $fileType) {
  213. $fileType = $this->_fileType;
  214. }
  215. if (empty(self::$_callbacks[$fileType])) {
  216. throw new \Exception($unsupportedText);
  217. }
  218. if (empty(self::$_callbacks[$fileType][$callbackType])) {
  219. throw new \Exception('Callback not found.');
  220. }
  221. return self::$_callbacks[$fileType][$callbackType];
  222. }
  223. /**
  224. * Fill image with main background color.
  225. *
  226. * Returns a color identifier.
  227. *
  228. * @param resource &$imageResourceTo
  229. * @return int
  230. * @throws \Exception
  231. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  232. */
  233. private function _fillBackgroundColor(&$imageResourceTo)
  234. {
  235. // try to keep transparency, if any
  236. if ($this->_keepTransparency) {
  237. $isAlpha = false;
  238. $transparentIndex = $this->_getTransparency($this->_imageHandler, $this->_fileType, $isAlpha);
  239. try {
  240. // fill truecolor png with alpha transparency
  241. if ($isAlpha) {
  242. if (!imagealphablending($imageResourceTo, false)) {
  243. throw new \Exception('Failed to set alpha blending for PNG image.');
  244. }
  245. $transparentAlphaColor = imagecolorallocatealpha($imageResourceTo, 0, 0, 0, 127);
  246. if (false === $transparentAlphaColor) {
  247. throw new \Exception('Failed to allocate alpha transparency for PNG image.');
  248. }
  249. if (!imagefill($imageResourceTo, 0, 0, $transparentAlphaColor)) {
  250. throw new \Exception('Failed to fill PNG image with alpha transparency.');
  251. }
  252. if (!imagesavealpha($imageResourceTo, true)) {
  253. throw new \Exception('Failed to save alpha transparency into PNG image.');
  254. }
  255. return $transparentAlphaColor;
  256. } elseif (false !== $transparentIndex) {
  257. // fill image with indexed non-alpha transparency
  258. $transparentColor = false;
  259. if ($transparentIndex >= 0 && $transparentIndex <= imagecolorstotal($this->_imageHandler)) {
  260. list($r, $g, $b) = array_values(imagecolorsforindex($this->_imageHandler, $transparentIndex));
  261. $transparentColor = imagecolorallocate($imageResourceTo, $r, $g, $b);
  262. }
  263. if (false === $transparentColor) {
  264. throw new \Exception('Failed to allocate transparent color for image.');
  265. }
  266. if (!imagefill($imageResourceTo, 0, 0, $transparentColor)) {
  267. throw new \Exception('Failed to fill image with transparency.');
  268. }
  269. imagecolortransparent($imageResourceTo, $transparentColor);
  270. return $transparentColor;
  271. }
  272. } catch (\Exception $e) {
  273. // fallback to default background color
  274. }
  275. }
  276. list($r, $g, $b) = $this->_backgroundColor;
  277. $color = imagecolorallocate($imageResourceTo, $r, $g, $b);
  278. if (!imagefill($imageResourceTo, 0, 0, $color)) {
  279. throw new \Exception("Failed to fill image background with color {$r} {$g} {$b}.");
  280. }
  281. return $color;
  282. }
  283. /**
  284. * Gives true for a PNG with alpha, false otherwise
  285. *
  286. * @param string $fileName
  287. * @return boolean
  288. */
  289. public function checkAlpha($fileName)
  290. {
  291. return (ord(file_get_contents($fileName, false, null, 25, 1)) & 6 & 4) == 4;
  292. }
  293. /**
  294. * Checks if image has alpha transparency
  295. *
  296. * @param resource $imageResource
  297. * @param int $fileType one of the constants IMAGETYPE_*
  298. * @param bool &$isAlpha
  299. * @param bool &$isTrueColor
  300. * @return boolean
  301. * @SuppressWarnings(PHPMD.BooleanGetMethodName)
  302. */
  303. private function _getTransparency($imageResource, $fileType, &$isAlpha = false, &$isTrueColor = false)
  304. {
  305. $isAlpha = false;
  306. $isTrueColor = false;
  307. // assume that transparency is supported by gif/png only
  308. if (IMAGETYPE_GIF === $fileType || IMAGETYPE_PNG === $fileType) {
  309. // check for specific transparent color
  310. $transparentIndex = imagecolortransparent($imageResource);
  311. if ($transparentIndex >= 0) {
  312. return $transparentIndex;
  313. } elseif (IMAGETYPE_PNG === $fileType) {
  314. // assume that truecolor PNG has transparency
  315. $isAlpha = $this->checkAlpha($this->_fileName);
  316. $isTrueColor = true;
  317. // -1
  318. return $transparentIndex;
  319. }
  320. }
  321. if (IMAGETYPE_JPEG === $fileType) {
  322. $isTrueColor = true;
  323. }
  324. return false;
  325. }
  326. /**
  327. * Change the image size
  328. *
  329. * @param null|int $frameWidth
  330. * @param null|int $frameHeight
  331. * @return void
  332. */
  333. public function resize($frameWidth = null, $frameHeight = null)
  334. {
  335. $dims = $this->_adaptResizeValues($frameWidth, $frameHeight);
  336. // create new image
  337. $isAlpha = false;
  338. $isTrueColor = false;
  339. $this->_getTransparency($this->_imageHandler, $this->_fileType, $isAlpha, $isTrueColor);
  340. if ($isTrueColor) {
  341. $newImage = imagecreatetruecolor($dims['frame']['width'], $dims['frame']['height']);
  342. } else {
  343. $newImage = imagecreate($dims['frame']['width'], $dims['frame']['height']);
  344. }
  345. if ($isAlpha) {
  346. $this->_saveAlpha($newImage);
  347. }
  348. // fill new image with required color
  349. $this->_fillBackgroundColor($newImage);
  350. if ($this->_imageHandler) {
  351. // resample source image and copy it into new frame
  352. imagecopyresampled(
  353. $newImage,
  354. $this->_imageHandler,
  355. $dims['dst']['x'],
  356. $dims['dst']['y'],
  357. $dims['src']['x'],
  358. $dims['src']['y'],
  359. $dims['dst']['width'],
  360. $dims['dst']['height'],
  361. $this->_imageSrcWidth,
  362. $this->_imageSrcHeight
  363. );
  364. }
  365. $this->imageDestroy();
  366. $this->_imageHandler = $newImage;
  367. $this->refreshImageDimensions();
  368. $this->_resized = true;
  369. }
  370. /**
  371. * Rotate image on specific angle
  372. *
  373. * @param int $angle
  374. * @return void
  375. */
  376. public function rotate($angle)
  377. {
  378. $rotatedImage = imagerotate($this->_imageHandler, $angle, $this->imageBackgroundColor);
  379. $this->imageDestroy();
  380. $this->_imageHandler = $rotatedImage;
  381. $this->refreshImageDimensions();
  382. }
  383. /**
  384. * Add watermark to image
  385. *
  386. * @param string $imagePath
  387. * @param int $positionX
  388. * @param int $positionY
  389. * @param int $opacity
  390. * @param bool $tile
  391. * @return void
  392. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  393. * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
  394. * @SuppressWarnings(PHPMD.UnusedLocalVariable)
  395. * @SuppressWarnings(PHPMD.UnusedFormalParameter)
  396. */
  397. public function watermark($imagePath, $positionX = 0, $positionY = 0, $opacity = 30, $tile = false)
  398. {
  399. list($watermarkSrcWidth, $watermarkSrcHeight, $watermarkFileType,) = $this->_getImageOptions($imagePath);
  400. $this->_getFileAttributes();
  401. $watermark = call_user_func(
  402. $this->_getCallback('create', $watermarkFileType, 'Unsupported watermark image format.'),
  403. $imagePath
  404. );
  405. $merged = false;
  406. if ($this->getWatermarkWidth() &&
  407. $this->getWatermarkHeight() &&
  408. $this->getWatermarkPosition() != self::POSITION_STRETCH
  409. ) {
  410. $newWatermark = imagecreatetruecolor($this->getWatermarkWidth(), $this->getWatermarkHeight());
  411. imagealphablending($newWatermark, false);
  412. $col = imagecolorallocate($newWatermark, 255, 255, 255);
  413. imagecolortransparent($newWatermark, $col);
  414. imagefilledrectangle($newWatermark, 0, 0, $this->getWatermarkWidth(), $this->getWatermarkHeight(), $col);
  415. imagesavealpha($newWatermark, true);
  416. imagecopyresampled(
  417. $newWatermark,
  418. $watermark,
  419. 0,
  420. 0,
  421. 0,
  422. 0,
  423. $this->getWatermarkWidth(),
  424. $this->getWatermarkHeight(),
  425. imagesx($watermark),
  426. imagesy($watermark)
  427. );
  428. $watermark = $newWatermark;
  429. }
  430. if ($this->getWatermarkPosition() == self::POSITION_TILE) {
  431. $tile = true;
  432. } elseif ($this->getWatermarkPosition() == self::POSITION_STRETCH) {
  433. $newWatermark = imagecreatetruecolor($this->_imageSrcWidth, $this->_imageSrcHeight);
  434. imagealphablending($newWatermark, false);
  435. $col = imagecolorallocate($newWatermark, 255, 255, 255);
  436. imagecolortransparent($newWatermark, $col);
  437. imagefilledrectangle($newWatermark, 0, 0, $this->_imageSrcWidth, $this->_imageSrcHeight, $col);
  438. imagesavealpha($newWatermark, true);
  439. imagecopyresampled(
  440. $newWatermark,
  441. $watermark,
  442. 0,
  443. 0,
  444. 0,
  445. 0,
  446. $this->_imageSrcWidth,
  447. $this->_imageSrcHeight,
  448. imagesx($watermark),
  449. imagesy($watermark)
  450. );
  451. $watermark = $newWatermark;
  452. } elseif ($this->getWatermarkPosition() == self::POSITION_CENTER) {
  453. $positionX = $this->_imageSrcWidth / 2 - imagesx($watermark) / 2;
  454. $positionY = $this->_imageSrcHeight / 2 - imagesy($watermark) / 2;
  455. $this->imagecopymergeWithAlphaFix(
  456. $this->_imageHandler,
  457. $watermark,
  458. $positionX,
  459. $positionY,
  460. 0,
  461. 0,
  462. imagesx($watermark),
  463. imagesy($watermark),
  464. $this->getWatermarkImageOpacity()
  465. );
  466. } elseif ($this->getWatermarkPosition() == self::POSITION_TOP_RIGHT) {
  467. $positionX = $this->_imageSrcWidth - imagesx($watermark);
  468. $this->imagecopymergeWithAlphaFix(
  469. $this->_imageHandler,
  470. $watermark,
  471. $positionX,
  472. $positionY,
  473. 0,
  474. 0,
  475. imagesx($watermark),
  476. imagesy($watermark),
  477. $this->getWatermarkImageOpacity()
  478. );
  479. } elseif ($this->getWatermarkPosition() == self::POSITION_TOP_LEFT) {
  480. $this->imagecopymergeWithAlphaFix(
  481. $this->_imageHandler,
  482. $watermark,
  483. $positionX,
  484. $positionY,
  485. 0,
  486. 0,
  487. imagesx($watermark),
  488. imagesy($watermark),
  489. $this->getWatermarkImageOpacity()
  490. );
  491. } elseif ($this->getWatermarkPosition() == self::POSITION_BOTTOM_RIGHT) {
  492. $positionX = $this->_imageSrcWidth - imagesx($watermark);
  493. $positionY = $this->_imageSrcHeight - imagesy($watermark);
  494. $this->imagecopymergeWithAlphaFix(
  495. $this->_imageHandler,
  496. $watermark,
  497. $positionX,
  498. $positionY,
  499. 0,
  500. 0,
  501. imagesx($watermark),
  502. imagesy($watermark),
  503. $this->getWatermarkImageOpacity()
  504. );
  505. } elseif ($this->getWatermarkPosition() == self::POSITION_BOTTOM_LEFT) {
  506. $positionY = $this->_imageSrcHeight - imagesy($watermark);
  507. $this->imagecopymergeWithAlphaFix(
  508. $this->_imageHandler,
  509. $watermark,
  510. $positionX,
  511. $positionY,
  512. 0,
  513. 0,
  514. imagesx($watermark),
  515. imagesy($watermark),
  516. $this->getWatermarkImageOpacity()
  517. );
  518. }
  519. if ($tile === false && $merged === false) {
  520. $this->imagecopymergeWithAlphaFix(
  521. $this->_imageHandler,
  522. $watermark,
  523. $positionX,
  524. $positionY,
  525. 0,
  526. 0,
  527. imagesx($watermark),
  528. imagesy($watermark),
  529. $this->getWatermarkImageOpacity()
  530. );
  531. } else {
  532. $offsetX = $positionX;
  533. $offsetY = $positionY;
  534. while ($offsetY <= $this->_imageSrcHeight + imagesy($watermark)) {
  535. while ($offsetX <= $this->_imageSrcWidth + imagesx($watermark)) {
  536. $this->imagecopymergeWithAlphaFix(
  537. $this->_imageHandler,
  538. $watermark,
  539. $offsetX,
  540. $offsetY,
  541. 0,
  542. 0,
  543. imagesx($watermark),
  544. imagesy($watermark),
  545. $this->getWatermarkImageOpacity()
  546. );
  547. $offsetX += imagesx($watermark);
  548. }
  549. $offsetX = $positionX;
  550. $offsetY += imagesy($watermark);
  551. }
  552. }
  553. imagedestroy($watermark);
  554. $this->refreshImageDimensions();
  555. }
  556. /**
  557. * Crop image
  558. *
  559. * @param int $top
  560. * @param int $left
  561. * @param int $right
  562. * @param int $bottom
  563. * @return bool
  564. */
  565. public function crop($top = 0, $left = 0, $right = 0, $bottom = 0)
  566. {
  567. if ($left == 0 && $top == 0 && $right == 0 && $bottom == 0) {
  568. return false;
  569. }
  570. $newWidth = $this->_imageSrcWidth - $left - $right;
  571. $newHeight = $this->_imageSrcHeight - $top - $bottom;
  572. $canvas = imagecreatetruecolor($newWidth, $newHeight);
  573. if ($this->_fileType == IMAGETYPE_PNG) {
  574. $this->_saveAlpha($canvas);
  575. }
  576. imagecopyresampled(
  577. $canvas,
  578. $this->_imageHandler,
  579. 0,
  580. 0,
  581. $left,
  582. $top,
  583. $newWidth,
  584. $newHeight,
  585. $newWidth,
  586. $newHeight
  587. );
  588. $this->imageDestroy();
  589. $this->_imageHandler = $canvas;
  590. $this->refreshImageDimensions();
  591. return true;
  592. }
  593. /**
  594. * Checks required dependencies
  595. *
  596. * @return void
  597. * @throws \Exception If some of dependencies are missing
  598. */
  599. public function checkDependencies()
  600. {
  601. foreach ($this->_requiredExtensions as $value) {
  602. if (!extension_loaded($value)) {
  603. throw new \Exception("Required PHP extension '{$value}' was not loaded.");
  604. }
  605. }
  606. }
  607. /**
  608. * Reassign image dimensions
  609. *
  610. * @return void
  611. */
  612. public function refreshImageDimensions()
  613. {
  614. $this->_imageSrcWidth = imagesx($this->_imageHandler);
  615. $this->_imageSrcHeight = imagesy($this->_imageHandler);
  616. }
  617. /**
  618. * Standard destructor. Destroy stored information about image
  619. */
  620. public function __destruct()
  621. {
  622. $this->imageDestroy();
  623. }
  624. /**
  625. * Helper function to free up memory associated with _imageHandler resource
  626. *
  627. * @return void
  628. */
  629. private function imageDestroy()
  630. {
  631. if (is_resource($this->_imageHandler)) {
  632. imagedestroy($this->_imageHandler);
  633. }
  634. }
  635. /**
  636. * Fixes saving PNG alpha channel
  637. *
  638. * @param resource $imageHandler
  639. * @return void
  640. */
  641. private function _saveAlpha($imageHandler)
  642. {
  643. $background = imagecolorallocate($imageHandler, 0, 0, 0);
  644. imagecolortransparent($imageHandler, $background);
  645. imagealphablending($imageHandler, false);
  646. imagesavealpha($imageHandler, true);
  647. }
  648. /**
  649. * Returns rgba array of the specified pixel
  650. *
  651. * @param int $x
  652. * @param int $y
  653. * @return array
  654. */
  655. public function getColorAt($x, $y)
  656. {
  657. $colorIndex = imagecolorat($this->_imageHandler, $x, $y);
  658. return imagecolorsforindex($this->_imageHandler, $colorIndex);
  659. }
  660. /**
  661. * Create Image from string
  662. *
  663. * @param string $text
  664. * @param string $font
  665. * @return \Magento\Framework\Image\Adapter\AbstractAdapter
  666. */
  667. public function createPngFromString($text, $font = '')
  668. {
  669. $error = false;
  670. $this->_resized = true;
  671. try {
  672. $this->_createImageFromTtfText($text, $font);
  673. } catch (\Exception $e) {
  674. $error = true;
  675. }
  676. if ($error || empty($this->_imageHandler)) {
  677. $this->_createImageFromText($text);
  678. }
  679. return $this;
  680. }
  681. /**
  682. * Create Image using standard font
  683. *
  684. * @param string $text
  685. * @return void
  686. */
  687. protected function _createImageFromText($text)
  688. {
  689. $width = imagefontwidth($this->_fontSize) * strlen($text);
  690. $height = imagefontheight($this->_fontSize);
  691. $this->_createEmptyImage($width, $height);
  692. $black = imagecolorallocate($this->_imageHandler, 0, 0, 0);
  693. imagestring($this->_imageHandler, $this->_fontSize, 0, 0, $text, $black);
  694. }
  695. /**
  696. * Create Image using ttf font
  697. *
  698. * Note: This function requires both the GD library and the FreeType library
  699. *
  700. * @param string $text
  701. * @param string $font
  702. * @return void
  703. * @throws \Exception
  704. */
  705. protected function _createImageFromTtfText($text, $font)
  706. {
  707. $boundingBox = imagettfbbox($this->_fontSize, 0, $font, $text);
  708. $width = abs($boundingBox[4] - $boundingBox[0]);
  709. $height = abs($boundingBox[5] - $boundingBox[1]);
  710. $this->_createEmptyImage($width, $height);
  711. $black = imagecolorallocate($this->_imageHandler, 0, 0, 0);
  712. $result = imagettftext(
  713. $this->_imageHandler,
  714. $this->_fontSize,
  715. 0,
  716. 0,
  717. $height - $boundingBox[1],
  718. $black,
  719. $font,
  720. $text
  721. );
  722. if ($result === false) {
  723. throw new \Exception('Unable to create TTF text');
  724. }
  725. }
  726. /**
  727. * Create empty image with transparent background
  728. *
  729. * @param int $width
  730. * @param int $height
  731. * @return void
  732. */
  733. protected function _createEmptyImage($width, $height)
  734. {
  735. $this->_fileType = IMAGETYPE_PNG;
  736. $image = imagecreatetruecolor($width, $height);
  737. $colorWhite = imagecolorallocatealpha($image, 255, 255, 255, 127);
  738. imagealphablending($image, true);
  739. imagesavealpha($image, true);
  740. imagefill($image, 0, 0, $colorWhite);
  741. $this->imageDestroy();
  742. $this->_imageHandler = $image;
  743. }
  744. /**
  745. * Fix an issue with the usage of imagecopymerge where the alpha channel is lost
  746. *
  747. * @param resource $dst_im
  748. * @param resource $src_im
  749. * @param int $dst_x
  750. * @param int $dst_y
  751. * @param int $src_x
  752. * @param int $src_y
  753. * @param int $src_w
  754. * @param int $src_h
  755. * @param int $pct
  756. *
  757. * @return bool
  758. */
  759. private function imagecopymergeWithAlphaFix(
  760. $dst_im,
  761. $src_im,
  762. $dst_x,
  763. $dst_y,
  764. $src_x,
  765. $src_y,
  766. $src_w,
  767. $src_h,
  768. $pct
  769. ) {
  770. if ($pct >= 100) {
  771. return imagecopy($dst_im, $src_im, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h);
  772. }
  773. if ($pct < 0) {
  774. return false;
  775. }
  776. $sizeX = imagesx($src_im);
  777. $sizeY = imagesy($src_im);
  778. if (false === $sizeX || false === $sizeY) {
  779. return false;
  780. }
  781. $tmpImg = imagecreatetruecolor($src_w, $src_h);
  782. if (false === $tmpImg) {
  783. return false;
  784. }
  785. if (false === imagealphablending($tmpImg, false)) {
  786. return false;
  787. }
  788. if (false === imagecopy($tmpImg, $src_im, 0, 0, 0, 0, $sizeX, $sizeY)) {
  789. return false;
  790. }
  791. $transparancy = 127 - (($pct*127)/100);
  792. if (false === imagefilter($tmpImg, IMG_FILTER_COLORIZE, 0, 0, 0, $transparancy)) {
  793. return false;
  794. }
  795. $result = imagecopy($dst_im, $tmpImg, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h);
  796. imagedestroy($tmpImg);
  797. return $result;
  798. }
  799. }