BaseImage.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. <?php
  2. /**
  3. * @link http://www.yiiframework.com/
  4. * @copyright Copyright (c) 2008 Yii Software LLC
  5. * @license http://www.yiiframework.com/license/
  6. */
  7. namespace yii\imagine;
  8. use Yii;
  9. use Imagine\Image\Box;
  10. use Imagine\Image\BoxInterface;
  11. use Imagine\Image\Color;
  12. use Imagine\Image\ImageInterface;
  13. use Imagine\Image\ImagineInterface;
  14. use Imagine\Image\ManipulatorInterface;
  15. use Imagine\Image\Point;
  16. use yii\base\InvalidConfigException;
  17. use yii\base\InvalidParamException;
  18. use yii\helpers\ArrayHelper;
  19. /**
  20. * BaseImage provides concrete implementation for [[Image]].
  21. *
  22. * Do not use BaseImage. Use [[Image]] instead.
  23. *
  24. * @author Antonio Ramirez <amigo.cobos@gmail.com>
  25. * @author Qiang Xue <qiang.xue@gmail.com>
  26. * @since 2.0
  27. */
  28. class BaseImage
  29. {
  30. /**
  31. * GD2 driver definition for Imagine implementation using the GD library.
  32. */
  33. const DRIVER_GD2 = 'gd2';
  34. /**
  35. * imagick driver definition.
  36. */
  37. const DRIVER_IMAGICK = 'imagick';
  38. /**
  39. * gmagick driver definition.
  40. */
  41. const DRIVER_GMAGICK = 'gmagick';
  42. /**
  43. * @var array|string the driver to use. This can be either a single driver name or an array of driver names.
  44. * If the latter, the first available driver will be used.
  45. */
  46. public static $driver = [self::DRIVER_GMAGICK, self::DRIVER_IMAGICK, self::DRIVER_GD2];
  47. /**
  48. * @var ImagineInterface instance.
  49. */
  50. private static $_imagine;
  51. /**
  52. * @var string background color to use when creating thumbnails in `ImageInterface::THUMBNAIL_INSET` mode with
  53. * both width and height specified. Default is white.
  54. *
  55. * @since 2.0.4
  56. */
  57. public static $thumbnailBackgroundColor = 'FFF';
  58. /**
  59. * @var string background alpha (transparency) to use when creating thumbnails in `ImageInterface::THUMBNAIL_INSET`
  60. * mode with both width and height specified. Default is solid.
  61. *
  62. * @since 2.0.4
  63. */
  64. public static $thumbnailBackgroundAlpha = 100;
  65. /**
  66. * Returns the `Imagine` object that supports various image manipulations.
  67. * @return ImagineInterface the `Imagine` object
  68. */
  69. public static function getImagine()
  70. {
  71. if (self::$_imagine === null) {
  72. self::$_imagine = static::createImagine();
  73. }
  74. return self::$_imagine;
  75. }
  76. /**
  77. * @param ImagineInterface $imagine the `Imagine` object.
  78. */
  79. public static function setImagine($imagine)
  80. {
  81. self::$_imagine = $imagine;
  82. }
  83. /**
  84. * Creates an `Imagine` object based on the specified [[driver]].
  85. * @return ImagineInterface the new `Imagine` object
  86. * @throws InvalidConfigException if [[driver]] is unknown or the system doesn't support any [[driver]].
  87. */
  88. protected static function createImagine()
  89. {
  90. foreach ((array) static::$driver as $driver) {
  91. switch ($driver) {
  92. case self::DRIVER_GMAGICK:
  93. if (class_exists('Gmagick', false)) {
  94. return new \Imagine\Gmagick\Imagine();
  95. }
  96. break;
  97. case self::DRIVER_IMAGICK:
  98. if (class_exists('Imagick', false)) {
  99. return new \Imagine\Imagick\Imagine();
  100. }
  101. break;
  102. case self::DRIVER_GD2:
  103. if (function_exists('gd_info')) {
  104. return new \Imagine\Gd\Imagine();
  105. }
  106. break;
  107. default:
  108. throw new InvalidConfigException("Unknown driver: $driver");
  109. }
  110. }
  111. throw new InvalidConfigException("Your system does not support any of these drivers: " . implode(',', (array) static::$driver));
  112. }
  113. /**
  114. * Crops an image.
  115. *
  116. * For example,
  117. *
  118. * ~~~
  119. * $obj->crop('path\to\image.jpg', 200, 200, [5, 5]);
  120. *
  121. * $point = new \Imagine\Image\Point(5, 5);
  122. * $obj->crop('path\to\image.jpg', 200, 200, $point);
  123. * ~~~
  124. *
  125. * @param string $filename the image file path or path alias.
  126. * @param integer $width the crop width
  127. * @param integer $height the crop height
  128. * @param array $start the starting point. This must be an array with two elements representing `x` and `y` coordinates.
  129. * @return ImageInterface
  130. * @throws InvalidParamException if the `$start` parameter is invalid
  131. */
  132. public static function crop($filename, $width, $height, array $start = [0, 0])
  133. {
  134. if (!isset($start[0], $start[1])) {
  135. throw new InvalidParamException('$start must be an array of two elements.');
  136. }
  137. return static::getImagine()
  138. ->open(Yii::getAlias($filename))
  139. ->copy()
  140. ->crop(new Point($start[0], $start[1]), new Box($width, $height));
  141. }
  142. /**
  143. * Creates a thumbnail image.
  144. *
  145. * If one of thumbnail dimensions is set to `null`, another one is calculated automatically based on aspect ratio of
  146. * original image. Note that calculated thumbnail dimension may vary depending on the source image in this case.
  147. *
  148. * If both dimensions are specified, resulting thumbnail would be exactly the width and height specified. How it's
  149. * achieved depends on the mode.
  150. *
  151. * If `ImageInterface::THUMBNAIL_OUTBOUND` mode is used, which is default, then the thumbnail is scaled so that
  152. * its smallest side equals the length of the corresponding side in the original image. Any excess outside of
  153. * the scaled thumbnail’s area will be cropped, and the returned thumbnail will have the exact width and height
  154. * specified.
  155. *
  156. * If thumbnail mode is `ImageInterface::THUMBNAIL_INSET`, the original image is scaled down so it is fully
  157. * contained within the thumbnail dimensions. The rest is filled with background that could be configured via
  158. * [[Image::$thumbnailBackgroundColor]] and [[Image::$thumbnailBackgroundAlpha]].
  159. *
  160. * @param string $filename the image file path or path alias.
  161. * @param integer $width the width in pixels to create the thumbnail
  162. * @param integer $height the height in pixels to create the thumbnail
  163. * @param string $mode mode of resizing original image to use in case both width and height specified
  164. * @return ImageInterface
  165. */
  166. public static function thumbnail($filename, $width, $height, $mode = ManipulatorInterface::THUMBNAIL_OUTBOUND)
  167. {
  168. $img = static::getImagine()->open(Yii::getAlias($filename));
  169. $sourceBox = $img->getSize();
  170. $thumbnailBox = static::getThumbnailBox($sourceBox, $width, $height);
  171. if (($sourceBox->getWidth() <= $thumbnailBox->getWidth() && $sourceBox->getHeight() <= $thumbnailBox->getHeight()) || (!$thumbnailBox->getWidth() && !$thumbnailBox->getHeight())) {
  172. return $img->copy();
  173. }
  174. $img = $img->thumbnail($thumbnailBox, $mode);
  175. if ($mode == ManipulatorInterface::THUMBNAIL_OUTBOUND) {
  176. return $img;
  177. }
  178. $size = $img->getSize();
  179. if ($size->getWidth() == $width && $size->getHeight() == $height) {
  180. return $img;
  181. }
  182. // create empty image to preserve aspect ratio of thumbnail
  183. $thumb = static::getImagine()->create($thumbnailBox, new Color(static::$thumbnailBackgroundColor, static::$thumbnailBackgroundAlpha));
  184. // calculate points
  185. $startX = 0;
  186. $startY = 0;
  187. if ($size->getWidth() < $width) {
  188. $startX = ceil($width - $size->getWidth()) / 2;
  189. }
  190. if ($size->getHeight() < $height) {
  191. $startY = ceil($height - $size->getHeight()) / 2;
  192. }
  193. $thumb->paste($img, new Point($startX, $startY));
  194. return $thumb;
  195. }
  196. /**
  197. * Adds a watermark to an existing image.
  198. * @param string $filename the image file path or path alias.
  199. * @param string $watermarkFilename the file path or path alias of the watermark image.
  200. * @param array $start the starting point. This must be an array with two elements representing `x` and `y` coordinates.
  201. * @return ImageInterface
  202. * @throws InvalidParamException if `$start` is invalid
  203. */
  204. public static function watermark($filename, $watermarkFilename, array $start = [0, 0])
  205. {
  206. if (!isset($start[0], $start[1])) {
  207. throw new InvalidParamException('$start must be an array of two elements.');
  208. }
  209. $img = static::getImagine()->open(Yii::getAlias($filename));
  210. $watermark = static::getImagine()->open(Yii::getAlias($watermarkFilename));
  211. $img->paste($watermark, new Point($start[0], $start[1]));
  212. return $img;
  213. }
  214. /**
  215. * Draws a text string on an existing image.
  216. * @param string $filename the image file path or path alias.
  217. * @param string $text the text to write to the image
  218. * @param string $fontFile the file path or path alias
  219. * @param array $start the starting position of the text. This must be an array with two elements representing `x` and `y` coordinates.
  220. * @param array $fontOptions the font options. The following options may be specified:
  221. *
  222. * - color: The font color. Defaults to "fff".
  223. * - size: The font size. Defaults to 12.
  224. * - angle: The angle to use to write the text. Defaults to 0.
  225. *
  226. * @return ImageInterface
  227. * @throws InvalidParamException if `$fontOptions` is invalid
  228. */
  229. public static function text($filename, $text, $fontFile, array $start = [0, 0], array $fontOptions = [])
  230. {
  231. if (!isset($start[0], $start[1])) {
  232. throw new InvalidParamException('$start must be an array of two elements.');
  233. }
  234. $fontSize = ArrayHelper::getValue($fontOptions, 'size', 12);
  235. $fontColor = ArrayHelper::getValue($fontOptions, 'color', 'fff');
  236. $fontAngle = ArrayHelper::getValue($fontOptions, 'angle', 0);
  237. $img = static::getImagine()->open(Yii::getAlias($filename));
  238. $font = static::getImagine()->font(Yii::getAlias($fontFile), $fontSize, new Color($fontColor));
  239. $img->draw()->text($text, $font, new Point($start[0], $start[1]), $fontAngle);
  240. return $img;
  241. }
  242. /**
  243. * Adds a frame around of the image. Please note that the image size will increase by `$margin` x 2.
  244. * @param string $filename the full path to the image file
  245. * @param integer $margin the frame size to add around the image
  246. * @param string $color the frame color
  247. * @param integer $alpha the alpha value of the frame.
  248. * @return ImageInterface
  249. */
  250. public static function frame($filename, $margin = 20, $color = '666', $alpha = 100)
  251. {
  252. $img = static::getImagine()->open(Yii::getAlias($filename));
  253. $size = $img->getSize();
  254. $pasteTo = new Point($margin, $margin);
  255. $padColor = new Color($color, $alpha);
  256. $box = new Box($size->getWidth() + ceil($margin * 2), $size->getHeight() + ceil($margin * 2));
  257. $image = static::getImagine()->create($box, $padColor);
  258. $image->paste($img, $pasteTo);
  259. return $image;
  260. }
  261. /**
  262. * Returns box for a thumbnail to be created. If one of the dimensions is set to `null`, another one is calculated
  263. * automatically based on width to height ratio of original image box.
  264. *
  265. * @param BoxInterface $sourceBox original image box
  266. * @param integer $width thumbnail width
  267. * @param integer $height thumbnail height
  268. * @return BoxInterface thumbnail box
  269. *
  270. * @since 2.0.4
  271. */
  272. protected static function getThumbnailBox(BoxInterface $sourceBox, $width, $height)
  273. {
  274. if ($width !== null && $height !== null) {
  275. return new Box($width, $height);
  276. }
  277. if ($width === null && $height === null) {
  278. throw new InvalidParamException('Width and height cannot be null at same time.');
  279. }
  280. $ratio = $sourceBox->getWidth() / $sourceBox->getHeight();
  281. if ($height === null) {
  282. $height = ceil($width / $ratio);
  283. } else {
  284. $width = ceil($height * $ratio);
  285. }
  286. return new Box($width, $height);
  287. }
  288. }