AbstractAdapter.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727
  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. use Magento\Framework\App\Filesystem\DirectoryList;
  8. /**
  9. * @file Abstract.php
  10. * @author Magento Core Team <core@magentocommerce.com>
  11. * @SuppressWarnings(PHPMD.TooManyFields)
  12. */
  13. abstract class AbstractAdapter implements AdapterInterface
  14. {
  15. /**
  16. * Background color
  17. * @var int|string
  18. */
  19. public $imageBackgroundColor = 0;
  20. /**
  21. * Position constants
  22. */
  23. const POSITION_TOP_LEFT = 'top-left';
  24. const POSITION_TOP_RIGHT = 'top-right';
  25. const POSITION_BOTTOM_LEFT = 'bottom-left';
  26. const POSITION_BOTTOM_RIGHT = 'bottom-right';
  27. const POSITION_STRETCH = 'stretch';
  28. const POSITION_TILE = 'tile';
  29. const POSITION_CENTER = 'center';
  30. /**
  31. * Default font size
  32. */
  33. const DEFAULT_FONT_SIZE = 15;
  34. /**
  35. * @var int
  36. */
  37. protected $_fileType;
  38. /**
  39. * @var string
  40. */
  41. protected $_fileName;
  42. /**
  43. * @var string
  44. */
  45. protected $_fileMimeType;
  46. /**
  47. * @var string
  48. */
  49. protected $_fileSrcName;
  50. /**
  51. * @var string
  52. */
  53. protected $_fileSrcPath;
  54. /**
  55. * @var resource
  56. */
  57. protected $_imageHandler;
  58. /**
  59. * @var int
  60. */
  61. protected $_imageSrcWidth;
  62. /**
  63. * @var int
  64. */
  65. protected $_imageSrcHeight;
  66. /**
  67. * @var array
  68. */
  69. protected $_requiredExtensions;
  70. /**
  71. * @var string
  72. */
  73. protected $_watermarkPosition;
  74. /**
  75. * @var int
  76. */
  77. protected $_watermarkWidth;
  78. /**
  79. * @var int
  80. */
  81. protected $_watermarkHeight;
  82. /**
  83. * @var int
  84. */
  85. protected $_watermarkImageOpacity;
  86. /**
  87. * @var int
  88. */
  89. protected $_quality;
  90. /**
  91. * @var int
  92. */
  93. protected $_fontSize = self::DEFAULT_FONT_SIZE;
  94. /**
  95. * @var bool
  96. */
  97. protected $_keepAspectRatio;
  98. /**
  99. * @var bool
  100. */
  101. protected $_keepFrame;
  102. /**
  103. * @var bool
  104. */
  105. protected $_keepTransparency;
  106. /**
  107. * @var array
  108. */
  109. protected $_backgroundColor;
  110. /**
  111. * @var bool
  112. */
  113. protected $_constrainOnly;
  114. /**
  115. * Filesystem instance
  116. *
  117. * @var \Magento\Framework\Filesystem
  118. */
  119. protected $_filesystem;
  120. /**
  121. * @var \Magento\Framework\Filesystem\Directory\Write
  122. */
  123. protected $directoryWrite;
  124. /**
  125. * @var \Psr\Log\LoggerInterface
  126. */
  127. protected $logger;
  128. /**
  129. * Open image for processing
  130. *
  131. * @param string $fileName
  132. * @return void
  133. */
  134. abstract public function open($fileName);
  135. /**
  136. * Save image to specific path.
  137. * If some folders of path does not exist they will be created
  138. *
  139. * @param null|string $destination
  140. * @param null|string $newName
  141. * @return void
  142. * @throws \Exception If destination path is not writable
  143. */
  144. abstract public function save($destination = null, $newName = null);
  145. /**
  146. * Render image and return its binary contents
  147. *
  148. * @return string
  149. */
  150. abstract public function getImage();
  151. /**
  152. * Change the image size
  153. *
  154. * @param null|int $width
  155. * @param null|int $height
  156. * @return void
  157. */
  158. abstract public function resize($width = null, $height = null);
  159. /**
  160. * Rotate image on specific angle
  161. *
  162. * @param int $angle
  163. * @return void
  164. */
  165. abstract public function rotate($angle);
  166. /**
  167. * Crop image
  168. *
  169. * @param int $top
  170. * @param int $left
  171. * @param int $right
  172. * @param int $bottom
  173. * @return bool
  174. */
  175. abstract public function crop($top = 0, $left = 0, $right = 0, $bottom = 0);
  176. /**
  177. * Add watermark to image
  178. *
  179. * @param string $imagePath
  180. * @param int $positionX
  181. * @param int $positionY
  182. * @param int $opacity
  183. * @param bool $tile
  184. * @return void
  185. */
  186. abstract public function watermark($imagePath, $positionX = 0, $positionY = 0, $opacity = 30, $tile = false);
  187. /**
  188. * Checks required dependencies
  189. *
  190. * @return void
  191. * @throws \Exception If some of dependencies are missing
  192. */
  193. abstract public function checkDependencies();
  194. /**
  195. * Create Image from string
  196. *
  197. * @param string $text
  198. * @param string $font Path to font file
  199. * @return AbstractAdapter
  200. */
  201. abstract public function createPngFromString($text, $font = '');
  202. /**
  203. * Reassign image dimensions
  204. *
  205. * @return void
  206. */
  207. abstract public function refreshImageDimensions();
  208. /**
  209. * Returns rgba array of the specified pixel
  210. *
  211. * @param int $x
  212. * @param int $y
  213. * @return array
  214. */
  215. abstract public function getColorAt($x, $y);
  216. /**
  217. * Initialize default values
  218. *
  219. * @param \Magento\Framework\Filesystem $filesystem
  220. * @param \Psr\Log\LoggerInterface $logger
  221. * @param array $data
  222. * @SuppressWarnings(PHPMD.UnusedFormalParameter)
  223. */
  224. public function __construct(
  225. \Magento\Framework\Filesystem $filesystem,
  226. \Psr\Log\LoggerInterface $logger,
  227. array $data = []
  228. ) {
  229. $this->_filesystem = $filesystem;
  230. $this->logger = $logger;
  231. $this->directoryWrite = $this->_filesystem->getDirectoryWrite(DirectoryList::ROOT);
  232. }
  233. /**
  234. * Assign image width, height, fileMimeType to object properties
  235. *
  236. * @return string|null
  237. */
  238. public function getMimeType()
  239. {
  240. if ($this->_fileMimeType) {
  241. return $this->_fileMimeType;
  242. } else {
  243. $this->_fileMimeType = image_type_to_mime_type($this->getImageType());
  244. return $this->_fileMimeType;
  245. }
  246. }
  247. /**
  248. * Assign image width, height, fileType to object properties using getimagesize function
  249. *
  250. * @return int|null
  251. */
  252. public function getImageType()
  253. {
  254. if ($this->_fileType) {
  255. return $this->_fileType;
  256. } else {
  257. if ($this->_canProcess()) {
  258. list($this->_imageSrcWidth, $this->_imageSrcHeight, $this->_fileType) = getimagesize($this->_fileName);
  259. return $this->_fileType;
  260. }
  261. }
  262. return null;
  263. }
  264. /**
  265. * Retrieve Original Image Width
  266. *
  267. * @return int|null
  268. */
  269. public function getOriginalWidth()
  270. {
  271. $this->getImageType();
  272. return $this->_imageSrcWidth;
  273. }
  274. /**
  275. * Retrieve Original Image Height
  276. *
  277. * @return int|null
  278. */
  279. public function getOriginalHeight()
  280. {
  281. $this->getImageType();
  282. return $this->_imageSrcHeight;
  283. }
  284. /**
  285. * Set watermark position
  286. *
  287. * @param string $position
  288. * @return $this
  289. */
  290. public function setWatermarkPosition($position)
  291. {
  292. $this->_watermarkPosition = $position;
  293. return $this;
  294. }
  295. /**
  296. * Get watermark position
  297. *
  298. * @return string
  299. */
  300. public function getWatermarkPosition()
  301. {
  302. return $this->_watermarkPosition;
  303. }
  304. /**
  305. * Set watermark opacity
  306. *
  307. * @param int $imageOpacity
  308. * @return $this
  309. */
  310. public function setWatermarkImageOpacity($imageOpacity)
  311. {
  312. $this->_watermarkImageOpacity = $imageOpacity;
  313. return $this;
  314. }
  315. /**
  316. * Get watermark opacity
  317. *
  318. * @return int
  319. */
  320. public function getWatermarkImageOpacity()
  321. {
  322. return $this->_watermarkImageOpacity;
  323. }
  324. /**
  325. * Set watermark width
  326. *
  327. * @param int $width
  328. * @return $this
  329. */
  330. public function setWatermarkWidth($width)
  331. {
  332. $this->_watermarkWidth = $width;
  333. return $this;
  334. }
  335. /**
  336. * Get watermark width
  337. *
  338. * @return int
  339. */
  340. public function getWatermarkWidth()
  341. {
  342. return $this->_watermarkWidth;
  343. }
  344. /**
  345. * Set watermark height
  346. *
  347. * @param int $height
  348. * @return $this
  349. */
  350. public function setWatermarkHeight($height)
  351. {
  352. $this->_watermarkHeight = $height;
  353. return $this;
  354. }
  355. /**
  356. * Return watermark height
  357. *
  358. * @return int
  359. */
  360. public function getWatermarkHeight()
  361. {
  362. return $this->_watermarkHeight;
  363. }
  364. /**
  365. * Get/set keepAspectRatio
  366. *
  367. * @param bool $value
  368. * @return bool|\Magento\Framework\Image\Adapter\AbstractAdapter
  369. */
  370. public function keepAspectRatio($value = null)
  371. {
  372. if (null !== $value) {
  373. $this->_keepAspectRatio = (bool)$value;
  374. }
  375. return $this->_keepAspectRatio;
  376. }
  377. /**
  378. * Get/set keepFrame
  379. *
  380. * @param bool $value
  381. * @return bool
  382. */
  383. public function keepFrame($value = null)
  384. {
  385. if (null !== $value) {
  386. $this->_keepFrame = (bool)$value;
  387. }
  388. return $this->_keepFrame;
  389. }
  390. /**
  391. * Get/set keepTransparency
  392. *
  393. * @param bool $value
  394. * @return bool
  395. */
  396. public function keepTransparency($value = null)
  397. {
  398. if (null !== $value) {
  399. $this->_keepTransparency = (bool)$value;
  400. }
  401. return $this->_keepTransparency;
  402. }
  403. /**
  404. * Get/set constrainOnly
  405. *
  406. * @param bool $value
  407. * @return bool
  408. */
  409. public function constrainOnly($value = null)
  410. {
  411. if (null !== $value) {
  412. $this->_constrainOnly = (bool)$value;
  413. }
  414. return $this->_constrainOnly;
  415. }
  416. /**
  417. * Get/set quality, values in percentage from 0 to 100
  418. *
  419. * @param int $value
  420. * @return int
  421. */
  422. public function quality($value = null)
  423. {
  424. if (null !== $value) {
  425. $this->_quality = (int)$value;
  426. }
  427. return $this->_quality;
  428. }
  429. /**
  430. * Get/set keepBackgroundColor
  431. *
  432. * @param null|array $value
  433. * @return array|void
  434. */
  435. public function backgroundColor($value = null)
  436. {
  437. if (null !== $value) {
  438. if (!is_array($value) || 3 !== count($value)) {
  439. return;
  440. }
  441. foreach ($value as $color) {
  442. if (!is_integer($color) || $color < 0 || $color > 255) {
  443. return;
  444. }
  445. }
  446. }
  447. $this->_backgroundColor = $value;
  448. return $this->_backgroundColor;
  449. }
  450. /**
  451. * Assign file dirname and basename to object properties
  452. *
  453. * @return void
  454. */
  455. protected function _getFileAttributes()
  456. {
  457. $pathinfo = pathinfo($this->_fileName);
  458. $this->_fileSrcPath = $pathinfo['dirname'];
  459. $this->_fileSrcName = $pathinfo['basename'];
  460. }
  461. /**
  462. * Adapt resize values based on image configuration
  463. *
  464. * @param int $frameWidth
  465. * @param int $frameHeight
  466. * @return array
  467. * @throws \Exception
  468. */
  469. protected function _adaptResizeValues($frameWidth, $frameHeight)
  470. {
  471. $this->_checkDimensions($frameWidth, $frameHeight);
  472. // calculate lacking dimension
  473. if (!$this->_keepFrame && $this->_checkSrcDimensions()) {
  474. if (null === $frameWidth) {
  475. $frameWidth = round($frameHeight * ($this->_imageSrcWidth / $this->_imageSrcHeight));
  476. } elseif (null === $frameHeight) {
  477. $frameHeight = round($frameWidth * ($this->_imageSrcHeight / $this->_imageSrcWidth));
  478. }
  479. } else {
  480. if (null === $frameWidth) {
  481. $frameWidth = $frameHeight;
  482. } elseif (null === $frameHeight) {
  483. $frameHeight = $frameWidth;
  484. }
  485. }
  486. // define coordinates of image inside new frame
  487. $srcX = 0;
  488. $srcY = 0;
  489. list($dstWidth, $dstHeight) = $this->_checkAspectRatio($frameWidth, $frameHeight);
  490. // define position in center
  491. // TODO: add positions option
  492. $dstY = round(($frameHeight - $dstHeight) / 2);
  493. $dstX = round(($frameWidth - $dstWidth) / 2);
  494. // get rid of frame (fallback to zero position coordinates)
  495. if (!$this->_keepFrame) {
  496. $frameWidth = $dstWidth;
  497. $frameHeight = $dstHeight;
  498. $dstY = 0;
  499. $dstX = 0;
  500. }
  501. return [
  502. 'src' => ['x' => $srcX, 'y' => $srcY],
  503. 'dst' => ['x' => $dstX, 'y' => $dstY, 'width' => $dstWidth, 'height' => $dstHeight],
  504. // size for new image
  505. 'frame' => ['width' => $frameWidth, 'height' => $frameHeight]
  506. ];
  507. }
  508. /**
  509. * Check aspect ratio
  510. *
  511. * @param int $frameWidth
  512. * @param int $frameHeight
  513. * @return int[]
  514. */
  515. protected function _checkAspectRatio($frameWidth, $frameHeight)
  516. {
  517. $dstWidth = $frameWidth;
  518. $dstHeight = $frameHeight;
  519. if ($this->_keepAspectRatio && $this->_checkSrcDimensions()) {
  520. // do not make picture bigger, than it is, if required
  521. if ($this->_constrainOnly) {
  522. if ($frameWidth >= $this->_imageSrcWidth && $frameHeight >= $this->_imageSrcHeight) {
  523. $dstWidth = $this->_imageSrcWidth;
  524. $dstHeight = $this->_imageSrcHeight;
  525. }
  526. }
  527. // keep aspect ratio
  528. if ($this->_imageSrcWidth / $this->_imageSrcHeight >= $frameWidth / $frameHeight) {
  529. $dstHeight = round($dstWidth / $this->_imageSrcWidth * $this->_imageSrcHeight);
  530. } else {
  531. $dstWidth = round($dstHeight / $this->_imageSrcHeight * $this->_imageSrcWidth);
  532. }
  533. }
  534. return [$dstWidth, $dstHeight];
  535. }
  536. /**
  537. * Check Frame dimensions and throw exception if they are not valid
  538. *
  539. * @param int $frameWidth
  540. * @param int $frameHeight
  541. * @return void
  542. * @throws \Exception
  543. */
  544. protected function _checkDimensions($frameWidth, $frameHeight)
  545. {
  546. if ($frameWidth !== null && $frameWidth <= 0 ||
  547. $frameHeight !== null && $frameHeight <= 0 ||
  548. empty($frameWidth) && empty($frameHeight)
  549. ) {
  550. throw new \Exception('Invalid image dimensions.');
  551. }
  552. }
  553. /**
  554. * Return false if source width or height is empty
  555. *
  556. * @return bool
  557. */
  558. protected function _checkSrcDimensions()
  559. {
  560. return !empty($this->_imageSrcWidth) && !empty($this->_imageSrcHeight);
  561. }
  562. /**
  563. * Return information about image using getimagesize function
  564. *
  565. * @param string $filePath
  566. * @return array
  567. */
  568. protected function _getImageOptions($filePath)
  569. {
  570. return getimagesize($filePath);
  571. }
  572. /**
  573. * Return supported image formats
  574. *
  575. * @return string[]
  576. */
  577. public function getSupportedFormats()
  578. {
  579. return ['gif', 'jpeg', 'jpg', 'png'];
  580. }
  581. /**
  582. * Create destination folder if not exists and return full file path
  583. *
  584. * @param string $destination
  585. * @param string $newName
  586. * @return string
  587. * @throws \Exception
  588. */
  589. protected function _prepareDestination($destination = null, $newName = null)
  590. {
  591. if (empty($destination)) {
  592. $destination = $this->_fileSrcPath;
  593. } else {
  594. if (empty($newName)) {
  595. $info = pathinfo($destination);
  596. $newName = $info['basename'];
  597. $destination = $info['dirname'];
  598. }
  599. }
  600. if (empty($newName)) {
  601. $newFileName = $this->_fileSrcName;
  602. } else {
  603. $newFileName = $newName;
  604. }
  605. $fileName = $destination . '/' . $newFileName;
  606. if (!is_writable($destination)) {
  607. try {
  608. $this->directoryWrite->create($this->directoryWrite->getRelativePath($destination));
  609. } catch (\Magento\Framework\Exception\FileSystemException $e) {
  610. $this->logger->critical($e);
  611. throw new \Exception('Unable to write file into directory ' . $destination . '. Access forbidden.');
  612. }
  613. }
  614. return $fileName;
  615. }
  616. /**
  617. * Checks is adapter can work with image
  618. *
  619. * @return bool
  620. */
  621. protected function _canProcess()
  622. {
  623. return !empty($this->_fileName);
  624. }
  625. /**
  626. * Check - is this file an image
  627. *
  628. * @param string $filePath
  629. * @return bool
  630. * @throws \InvalidArgumentException
  631. */
  632. public function validateUploadFile($filePath)
  633. {
  634. if (!file_exists($filePath)) {
  635. throw new \InvalidArgumentException("File '{$filePath}' does not exists.");
  636. }
  637. if (!getimagesize($filePath)) {
  638. throw new \InvalidArgumentException('Disallowed file type.');
  639. }
  640. $this->checkDependencies();
  641. $this->open($filePath);
  642. return $this->getImageType() !== null;
  643. }
  644. }