PageRenderTime 37ms CodeModel.GetById 9ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/BoxUK/Obscura/ThumbnailFactory.php

http://github.com/boxuk/obscura
PHP | 401 lines | 183 code | 92 blank | 126 comment | 47 complexity | 2c0122c101b4171229ede9e1eec632eb MD5 | raw file
Possible License(s): GPL-3.0, MIT
  1. <?php
  2. namespace BoxUK\Obscura;
  3. use BoxUK\Obscura\ImageDecorator;
  4. use BoxUK\Obscura\ImageDecorator\Factory;
  5. use BoxUK\Obscura\ThumbnailFactory\Config;
  6. /**
  7. * Creates thumbnails for images
  8. *
  9. * @copyright Copyright (c) 2010, Box UK
  10. * @license http://opensource.org/licenses/mit-license.php MIT License
  11. * @link http://github.com/boxuk/obscura
  12. * @since 1.0
  13. */
  14. class ThumbnailFactory {
  15. /**
  16. * @var BoxUK\Obscura\ImageDecorator\Factory
  17. */
  18. private $factory;
  19. /**
  20. * The root location from which images will be loaded.
  21. *
  22. * @var string
  23. */
  24. private $inputDirectory;
  25. /**
  26. * The root location at which thumbnails will be saved
  27. *
  28. * @var string
  29. */
  30. private $outputDirectory;
  31. /**
  32. * Constructs a new instance of this class
  33. *
  34. * @param BoxUK\Obscura\ImageDecorator\Factory $factory
  35. */
  36. public function __construct( Factory $factory ) {
  37. $this->factory = $factory;
  38. }
  39. /**
  40. * Returns the path to the directory from which input images will be loaded.
  41. *
  42. * @return string
  43. */
  44. public function getInputDirectory() {
  45. return $this->inputDirectory;
  46. }
  47. /**
  48. * Sets the root directory from which images will be read.
  49. *
  50. * @param string $directory
  51. *
  52. * @throws InvalidArgumentException If the directory does not exist.
  53. */
  54. public function setInputDirectory( $directory ) {
  55. if(! is_dir($directory) ) {
  56. throw new \InvalidArgumentException( 'The given path is not a directory at "' . $directory . '"' );
  57. }
  58. $this->inputDirectory = $directory;
  59. }
  60. /**
  61. * Returns the path to the directory at which thumbnails will be saved.
  62. *
  63. * @return string
  64. */
  65. public function getOutputDirectory() {
  66. return $this->outputDirectory;
  67. }
  68. /**
  69. * Sets the root directory at which thumbnails will be output.
  70. *
  71. * @param string $directory
  72. *
  73. * @throws InvalidArgumentException If the directory does not exist or is not writable.
  74. */
  75. public function setOutputDirectory ( $directory ) {
  76. if(! is_dir($directory) || ! is_writable($directory)) {
  77. throw new \InvalidArgumentException( 'The destination directory is not writable at "' . $directory . '"' );
  78. }
  79. $this->outputDirectory = $directory;
  80. }
  81. /**
  82. * Creates a thumbnail for the given image according to the configuration object supplied.
  83. *
  84. * @param BoxUK\Obscura\ThumbnailFactory\Config $config
  85. *
  86. * @return string | boolean
  87. *
  88. * @throws BoxUK\Obscura\Exception if an output directory has not been set
  89. */
  90. public function createThumbnail(Config $config) {
  91. $pathToInputImage = $this->getPathToInputImage( $config );
  92. if(! strlen($this->outputDirectory)) {
  93. throw new Exception( 'The output directory has not been set' );
  94. }
  95. $image = $this->factory->loadImageFromFile( $pathToInputImage );
  96. $outputFilename = $this->getFilenameForThumbnail($config);
  97. $pathToThumbnail = $this->outputDirectory . DIRECTORY_SEPARATOR . $outputFilename;
  98. // If a thumbnail already exists for the configuration, do nothing
  99. if($config->getCachingEnabled() && $this->thumbnailExistsAndIsFresh($pathToInputImage, $pathToThumbnail)) {
  100. return $outputFilename;
  101. }
  102. if ($config->getCrop()) {
  103. $this->cropThumbnail($image, $config);
  104. }
  105. else {
  106. $this->resizeThumbnail($image, $config);
  107. }
  108. $this->mountThumbnail($image, $config);
  109. $success = $image->output($pathToThumbnail, $config->getImageType(), $config->getImageQuality());
  110. if(! $success) {
  111. return false;
  112. }
  113. return $outputFilename;
  114. }
  115. /**
  116. * Resizes the input image according to the user's configuration.
  117. *
  118. * @param BoxUK\Obscura\ImageDecorator $image
  119. * @param BoxUK\Obscura\ThumbnailFactory\Config $config
  120. *
  121. * @return ImageDecorator
  122. */
  123. private function resizeThumbnail(ImageDecorator $image, Config $config) {
  124. $width = $config->getWidth();
  125. $height = $config->getHeight();
  126. $constraint = $config->getSizeConstraint();
  127. // Nothing to do
  128. if(! $width && ! $height && ! $constraint) {
  129. return;
  130. }
  131. // If both width and height are set, then resize to those values
  132. if($width && $height) {
  133. return $image->resize($width, $height);
  134. }
  135. $aspectRatioLock = $config->getAspectRatioLock();
  136. // The user wants the image with the longest dimension to get this length
  137. if( $constraint ) {
  138. switch($image->getOrientation()) {
  139. case ImageDecorator::ORIENTATION_LANDSCAPE:
  140. $width = $constraint;
  141. $height = null;
  142. $aspectRatioLock = true;
  143. break;
  144. case ImageDecorator::ORIENTATION_PORTRAIT:
  145. $height = $constraint;
  146. $width = null;
  147. $aspectRatioLock = true;
  148. break;
  149. case ImageDecorator::ORIENTATION_SQUARE:
  150. default:
  151. $width = $height = $constraint;
  152. break;
  153. }
  154. }
  155. if($aspectRatioLock) {
  156. if($height) {
  157. $width = $image->calculateProportionalSizeForDimension($height, $image->getHeight(), $image->getWidth());
  158. }
  159. else {
  160. $height = $image->calculateProportionalSizeForDimension($width, $image->getWidth(), $image->getHeight());
  161. }
  162. }
  163. return $image->resize($width, $height);
  164. }
  165. /**
  166. * Crops the thumbnail according to the user's configuration.
  167. *
  168. * @param BoxUK\Obscura\ImageDecorator $image
  169. * @param BoxUK\Obscura\Config $config
  170. *
  171. * @return BoxUK\Obscura\ImageDecorator
  172. */
  173. private function cropThumbnail(ImageDecorator $image, Config $config) {
  174. $width = $config->getWidth();
  175. $height = $config->getHeight();
  176. // If both width and height are set, then crop to those values.
  177. if ($width && $height) {
  178. return $image->crop($width, $height);
  179. }
  180. else if ($width) {
  181. return $image->crop($width);
  182. }
  183. else if ($height) {
  184. return $image->crop(null, $height);
  185. }
  186. return;
  187. }
  188. /**
  189. * Mounts the thumbnail onto a background image according to the user's configuration.
  190. *
  191. * @param BoxUK\Obscura\ImageDecorator $image
  192. * @param BoxUK\Obscura\Config $config
  193. *
  194. * @return BoxUK\Obscura\ImageDecorator
  195. */
  196. private function mountThumbnail(ImageDecorator $image, Config $config) {
  197. $mountEnabled = $config->getMountEnabled();
  198. if(! $mountEnabled) {
  199. return $image;
  200. }
  201. $mountWidth = $config->getMountWidth();
  202. $mountHeight = $config->getMountHeight();
  203. $mountColor = $config->getMountColor();
  204. // If both a width and a height have been specified, use those
  205. if($mountWidth && $mountHeight) {
  206. return $image->mount($mountWidth, $mountHeight, $mountColor);
  207. }
  208. // Else if neither have been specified, create a square using the length of the longest dimension
  209. else if(! $mountWidth && ! $mountHeight) {
  210. if($image->getOrientation() == ImageDecorator::ORIENTATION_LANDSCAPE) {
  211. $mountWidth = $mountHeight = $image->getWidth();
  212. }
  213. else if($image->getOrientation() == ImageDecorator::ORIENTATION_PORTRAIT) {
  214. $mountWidth = $mountHeight = $image->getHeight();
  215. }
  216. }
  217. // If either the width or height of the mount remain unset, set them to equal their corresponding dimension
  218. if(! $mountWidth) {
  219. $mountWidth = $image->getWidth();
  220. }
  221. if(! $mountHeight) {
  222. $mountHeight = $image->getHeight();
  223. }
  224. $image->mount($mountWidth, $mountHeight, $mountColor);
  225. }
  226. /**
  227. * Returns the path to the input image
  228. *
  229. * @param BoxUK\Obscura\Config $config
  230. *
  231. * @return string
  232. *
  233. * @throws BoxUK\Obscura\Exception if the input image does not exist or is not readable
  234. */
  235. private function getPathToInputImage(Config $config) {
  236. $inputFilename = $config->getInputFilename();
  237. if(! strlen($inputFilename)) {
  238. throw new Exception( 'Expected an input filename' );
  239. }
  240. // The user may have specified an explicit path
  241. if(file_exists($inputFilename) && is_readable($inputFilename)) {
  242. return $inputFilename;
  243. }
  244. // Try to find the image relative to the input directory
  245. $pathToImage = $this->getInputDirectory() . \DIRECTORY_SEPARATOR . $inputFilename;
  246. if(file_exists($pathToImage) && is_readable($pathToImage)) {
  247. return $pathToImage;
  248. }
  249. throw new Exception('Input image does not exist.');
  250. }
  251. /**
  252. * Returns true if the thumbnail already exists and is more recent than the original image
  253. *
  254. * @param string $pathToImage
  255. * @param string $pathToThumbnail
  256. *
  257. * @return boolean
  258. */
  259. private function thumbnailExistsAndIsFresh($pathToImage, $pathToThumbnail) {
  260. return (file_exists($pathToThumbnail) && filemtime($pathToImage) < filemtime($pathToThumbnail));
  261. }
  262. /**
  263. * Creates a unique filename for a thumbnail based on the input parameters
  264. *
  265. * @param BoxUK\Obscura\ThumbnailFactory\Config $config
  266. *
  267. * @return string
  268. */
  269. private function getFilenameForThumbnail(Config $config) {
  270. $inputFilename = $config->getInputFilename();
  271. $outputFilename = $config->getOutputFilename();
  272. if(strlen($outputFilename)) {
  273. return $outputFilename;
  274. }
  275. $extension = $this->getExtensionForThumbnail($config);
  276. return sprintf(
  277. 'thumb-%s%s',
  278. md5(serialize($config)),
  279. $extension
  280. );
  281. }
  282. /**
  283. * Returns a file extension appropriate for the image type.
  284. *
  285. * @param BoxUK\Obscura\ThumbnailFactory\Config $config
  286. *
  287. * @return string
  288. */
  289. private function getExtensionForThumbnail(Config $config) {
  290. $imageType = $config->getImageType();
  291. // If the image type is changing, use an appropriate extension
  292. if($imageType) {
  293. switch ($imageType) {
  294. case \IMAGETYPE_JPEG:
  295. return '.jpg';
  296. case \IMAGETYPE_GIF:
  297. return '.gif';
  298. case \IMAGETYPE_PNG:
  299. return '.png';
  300. }
  301. }
  302. // Default to using the same extension as the input filename
  303. $inputFilename = $config->getInputFilename();
  304. if(stristr($inputFilename, '.')) {
  305. return substr($inputFilename, strrpos($inputFilename, '.'));
  306. }
  307. return null;
  308. }
  309. }