Image.php 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. <?php
  2. namespace yiins\web;
  3. use CHtml;
  4. use yiins\components\ImageGD;
  5. use yiins\Yii;
  6. class Image
  7. {
  8. protected $image;
  9. protected $imageCroppedPath;
  10. protected $imagePath;
  11. protected $width;
  12. protected $height;
  13. protected $cropRatio;
  14. protected $nestedDir;
  15. protected $showWatermark;
  16. protected $params = [];
  17. private final function __construct(array $options)
  18. {
  19. $this->params = Yii::app()->params['imagephp'] ?? [];
  20. $this->image = $options['image'] ?? '';
  21. $this->imagePath = realpath(Yii::web()->uploadSaver->uploadsPath . '/..' . $this->image);
  22. $this->width = $options['width'] ?? false;
  23. $this->height = $options['height'] ?? false;
  24. $this->cropRatio = $options['cropRatio'] ?? false;
  25. $this->showWatermark = $options['showWatermark'] ?? false;
  26. $this->croppedImage();
  27. if ($this->showWatermark && isset($this->params['watermarkPath'])) {
  28. $this->watermarkedImage($this->imageCroppedPath);
  29. }
  30. }
  31. protected function croppedImage()
  32. {
  33. $filename = sha1(basename($this->imagePath) . $this->width . $this->height . $this->cropRatio);
  34. $path = $this->getNestedDir($filename);
  35. $this->imageCroppedPath = Yii::web()->uploadSaver->uploadsPath . '/' . $path . '/' . $filename . '.' . $this->getExt();
  36. if (!file_exists($this->imageCroppedPath)) {
  37. $this->cropImage();
  38. }
  39. }
  40. protected function getNestedDir($fileName, $depth = 4)
  41. {
  42. if (!$this->nestedDir) {
  43. $len = strlen($fileName);
  44. $res = [];
  45. for ($i = 0; $i < $len && $i < $depth * 2; $i += 2) {
  46. $res[] = substr($fileName, $i, 2);
  47. @mkdir(Yii::web()->uploadSaver->uploadsPath . '/' . implode('/', $res), 0777);
  48. }
  49. $this->nestedDir = implode('/', $res);
  50. }
  51. return $this->nestedDir;
  52. }
  53. public function getExt()
  54. {
  55. $fileNameArr = explode('.', $this->imagePath);
  56. return array_pop($fileNameArr);
  57. }
  58. protected function cropImage()
  59. {
  60. // Get the size and MIME type of the requested image
  61. $size = getImageSize($this->imagePath);
  62. $width = $size[0];
  63. $height = $size[1];
  64. list($maxWidth, $maxHeight) = $this->calculateMaxes($width, $height);
  65. $offsetX = 0;
  66. $offsetY = 0;
  67. list($width, $height, $offsetX, $offsetY) = $this->calculateCrop($width, $height, $offsetX, $offsetY);
  68. // Setting up the ratios needed for resizing. We will compare these below to determine how to
  69. // resize the image (based on height or based on width)
  70. $xRatio = $maxWidth / $width;
  71. $yRatio = $maxHeight / $height;
  72. // If either a max width or max height are not specified, we default to something
  73. // large so the unspecified dimension isn't a constraint on our resized image.
  74. // If neither are specified but the color is, we aren't going to be resizing at
  75. // all, just coloring.
  76. if ($xRatio * $height < $maxHeight) { // Resize the image based on width
  77. $tnHeight = ceil($xRatio * $height);
  78. $tnWidth = $maxWidth;
  79. } else {
  80. // Resize the image based on height
  81. $tnWidth = ceil($yRatio * $width);
  82. $tnHeight = $maxHeight;
  83. }
  84. if ((int)ini_get('memory_limit') < 100) {
  85. ini_set('memory_limit', '100M');
  86. }
  87. ImageGD::resize($this->imagePath, $tnWidth, $tnHeight, $offsetX, $offsetY, $width, $height)
  88. ->quality(100)
  89. ->output($this->imageCroppedPath);
  90. }
  91. protected function calculateMaxes($width, $height)
  92. {
  93. $maxWidth = ($this->width !== false) ? (int)$this->width : 0;
  94. $maxHeight = ($this->height !== false) ? (int)$this->height : 0;
  95. if (!$maxWidth && $maxHeight) {
  96. $maxWidth = 99999999999999;
  97. } elseif ($maxWidth && !$maxHeight) {
  98. $maxHeight = 99999999999999;
  99. } elseif (!$maxWidth && !$maxHeight) {
  100. $maxWidth = $width;
  101. $maxHeight = $height;
  102. }
  103. return [$maxWidth, $maxHeight];
  104. }
  105. protected function calculateCrop($width, $height, $offsetX, $offsetY)
  106. {
  107. if ($this->cropRatio !== false) {
  108. $cropRatio = explode(':', (string)$this->cropRatio);
  109. if (count($cropRatio) == 2) {
  110. $ratioComputed = $width / $height;
  111. $cropRatioComputed = (float)$cropRatio[0] / (float)$cropRatio[1];
  112. if ($ratioComputed < $cropRatioComputed) { // Image is too tall so we will crop the top and bottom
  113. $origHeight = $height;
  114. $height = $width / $cropRatioComputed;
  115. $offsetY = ($origHeight - $height) / 2;
  116. } else {
  117. if ($ratioComputed > $cropRatioComputed) { // Image is too wide so we will crop off the left and right sides
  118. $origWidth = $width;
  119. $width = $height * $cropRatioComputed;
  120. $offsetX = ($origWidth - $width) / 2;
  121. }
  122. }
  123. }
  124. }
  125. return [$width, $height, $offsetX, $offsetY];
  126. }
  127. protected function watermarkedImage($from)
  128. {
  129. $filename = sha1(basename($this->imagePath) . $this->width . $this->height . $this->cropRatio . $this->showWatermark);
  130. $path = $this->getNestedDir($filename);
  131. $this->imageCroppedPath = Yii::web()->uploadSaver->uploadsPath . '/' . $path . '/' . $filename . '.' . $this->getExt();
  132. if (!file_exists($this->imageCroppedPath)) {
  133. $this->watermarkImage($from);
  134. }
  135. }
  136. public function watermarkImage($from)
  137. {
  138. if ((int)ini_get('memory_limit') < 100) {
  139. ini_set('memory_limit', '100M');
  140. }
  141. $wmPath = is_callable($this->params['watermarkPath']) ? $this->params['watermarkPath']() : $this->params['watermarkPath'];
  142. ImageGD::watermark($from, $wmPath,
  143. $this->params['watermarkPosition'] ?? false)->output($this->imageCroppedPath);
  144. }
  145. /**
  146. * @param string $image
  147. * @param int|boolean $width
  148. * @param int|boolean $height
  149. * @param string|boolean $cropRatio
  150. * @param array $options
  151. * @return string
  152. */
  153. public static function render($image, $width = false, $height = false, $cropRatio = false, $options = [])
  154. {
  155. if (strpos($image, '//') === 0 || preg_match('/https?:/', $image)) {
  156. return CHtml::image($image, $options['alt'] ?? '', $options);
  157. }
  158. if (empty($image) || !file_exists(Yii::web()->uploadSaver->uploadsPath . '/..' . $image)) {
  159. return '';
  160. }
  161. $options = \CMap::mergeArray([
  162. 'image' => $image,
  163. 'width' => $width,
  164. 'height' => $height,
  165. 'cropRatio' => $cropRatio,
  166. ], $options);
  167. return (new static($options))->printHtml($options);
  168. }
  169. protected function printHtml($options)
  170. {
  171. $alt = '';
  172. if (isset($options['alt'])) {
  173. $alt = $options['alt'];
  174. unset($options['alt']);
  175. }
  176. $htmlOptions = [];
  177. if (isset($options['class'])) {
  178. $htmlOptions['class'] = $options['class'];
  179. }
  180. if (isset($options['style'])) {
  181. $htmlOptions['style'] = $options['style'];
  182. }
  183. return CHtml::image(Yii::web()->baseUrl . '/'
  184. . ltrim(Yii::web()->uploadSaver->uploadsUrl, '/') . '/'
  185. . $this->getNestedDir(basename($this->imageCroppedPath)) . '/'
  186. . basename($this->imageCroppedPath), $alt, $htmlOptions);
  187. }
  188. public static function getImageUrl($image, $width = false, $height = false, $cropRatio = false, $options = [])
  189. {
  190. if (empty($image) || !file_exists(Yii::web()->uploadSaver->uploadsPath . '/..' . $image)) {
  191. return '';
  192. }
  193. $options = \CMap::mergeArray([
  194. 'image' => $image,
  195. 'width' => $width,
  196. 'height' => $height,
  197. 'cropRatio' => $cropRatio,
  198. ], $options);
  199. return (new static($options))->getUrl();
  200. }
  201. protected function getUrl()
  202. {
  203. return Yii::web()->baseUrl . '/'
  204. . ltrim(Yii::web()->uploadSaver->uploadsUrl, '/') . '/'
  205. . $this->getNestedDir(basename($this->imageCroppedPath)) . '/'
  206. . basename($this->imageCroppedPath);
  207. }
  208. }