Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Newscoop/newscoop/library/Newscoop/Image/ImageService.php /
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
869 lines (767 sloc)
25.8 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| /** | |
| * @package Newscoop | |
| * @copyright 2012 Sourcefabric o.p.s. | |
| * @license http://www.gnu.org/licenses/gpl-3.0.txt | |
| */ | |
| namespace Newscoop\Image; | |
| use Imagine\Image\Box; | |
| use Symfony\Component\Filesystem\Filesystem; | |
| use Symfony\Component\HttpFoundation\File\Exception\FileException; | |
| use Symfony\Component\HttpFoundation\File\UploadedFile; | |
| use Newscoop\Image\ImageInterface as NewscoopImageInterface; | |
| use Newscoop\Image\LocalImage; | |
| use Newscoop\Entity\User; | |
| use Newscoop\Exception\ResourcesConflictException; | |
| use Doctrine\ORM\NoResultException; | |
| /** | |
| * Image Service | |
| */ | |
| class ImageService | |
| { | |
| /** allows enlarging image (it only shrinks images by default) */ | |
| const ENLARGE = 1; | |
| /** will ignore aspect ratio */ | |
| const STRETCH = 2; | |
| /** fits in given area */ | |
| const FIT = 0; | |
| /** fills (and even overflows) given area */ | |
| const FILL = 4; | |
| /** | |
| * @var array | |
| */ | |
| protected $config = array(); | |
| /** | |
| * @var Doctrine\ORM\EntityManager | |
| */ | |
| protected $orm; | |
| protected $cacheService; | |
| /** | |
| * @var array | |
| */ | |
| protected $supportedTypes = array( | |
| 'image/jpeg', | |
| 'image/jpg', | |
| 'image/png', | |
| 'image/gif', | |
| ); | |
| /** | |
| * @param array $config | |
| * @param Doctrine\ORM\EntityManager $orm | |
| */ | |
| public function __construct(array $config, \Doctrine\ORM\EntityManager $orm, $cacheService) | |
| { | |
| $this->config = $config; | |
| $this->orm = $orm; | |
| $this->cacheService = $cacheService; | |
| } | |
| public static function getImagine() | |
| { | |
| try { | |
| $imagine = new \Imagine\Imagick\Imagine(); | |
| } catch (\Imagine\Exception\RuntimeException $e) { | |
| $imagine = new \Imagine\Gd\Imagine(); | |
| } | |
| return $imagine; | |
| } | |
| /** | |
| * Upload image and create entity | |
| * | |
| * @param UploadedFile $file | |
| * @param array $attributes | |
| * @param ImageInterface $image | |
| * | |
| * @return LocalImage | |
| */ | |
| public function upload(UploadedFile $file, array $attributes, ImageInterface $image = null, $keepRatio = true) | |
| { | |
| $filesystem = new Filesystem(); | |
| $imagine = self::getImagine(); | |
| $mimeType = $file->getClientMimeType(); | |
| if (!in_array($mimeType, $this->supportedTypes)) { | |
| throw new \InvalidArgumentException('Unsupported image type '.$mimeType.'.'); | |
| } | |
| if (!file_exists($this->config['image_path']) || !is_writable($this->config['image_path'])) { | |
| throw new FileException('Directory '.$this->config['image_path'].' is not writable'); | |
| } | |
| if (!file_exists($this->config['thumbnail_path']) || !is_writable($this->config['thumbnail_path'])) { | |
| throw new FileException('Directory '.$this->config['thumbnail_path'].' is not writable'); | |
| } | |
| $attributes = array_merge(array( | |
| 'content_type' => $mimeType, | |
| ), $attributes); | |
| if (!is_null($image)) { | |
| if (file_exists($image->getPath())) { | |
| $filesystem->remove($image->getPath()); | |
| } | |
| if (file_exists($image->getThumbnailPath())) { | |
| unlink($this->config['thumbnail_path'] . $image->getThumbnailPath(true)); | |
| } | |
| } else { | |
| $image = new LocalImage($file->getClientOriginalName()); | |
| $image->setCreated(new \DateTime()); | |
| $this->orm->persist($image); | |
| } | |
| list($width, $height) = getimagesize($file->getRealPath()); | |
| $image->setWidth($width); | |
| $image->setHeight($height); | |
| $this->fillImage($image, $attributes); | |
| $this->orm->flush(); | |
| $imagePath = $this->generateImagePath($image->getId(), $file->getClientOriginalExtension()); | |
| $thumbnailPath = $this->generateThumbnailPath($image->getId(), $file->getClientOriginalExtension()); | |
| $image->setBasename($this->generateImagePath($image->getId(), $file->getClientOriginalExtension(), true)); | |
| $image->setThumbnailPath($this->generateThumbnailPath($image->getId(), $file->getClientOriginalExtension(), true)); | |
| $this->orm->flush(); | |
| try { | |
| $file->move($this->config['image_path'], $this->generateImagePath($image->getId(), $file->getClientOriginalExtension(), true)); | |
| $filesystem->chmod($imagePath, 0644); | |
| if ($keepRatio) { | |
| $ratioOrig = $width / $height; | |
| $ratioNew = $this->config['thumbnail_max_size'] / $this->config['thumbnail_max_size']; | |
| if ($ratioNew > $ratioOrig) { | |
| $newImageWidth = $this->config['thumbnail_max_size'] * $ratioOrig; | |
| $newImageHeight = $this->config['thumbnail_max_size']; | |
| } else { | |
| $newImageWidth = $this->config['thumbnail_max_size']; | |
| $newImageHeight = $this->config['thumbnail_max_size'] / $ratioOrig; | |
| } | |
| } else { | |
| $newImageWidth = $this->config['thumbnail_max_size']; | |
| $newImageHeight = $this->config['thumbnail_max_size']; | |
| } | |
| $imagine->open($imagePath) | |
| ->resize(new Box($newImageWidth, $newImageHeight)) | |
| ->save($thumbnailPath, array( | |
| 'quality' => 90, //from 0 to 100 | |
| )); | |
| $filesystem->chmod($thumbnailPath, 0644); | |
| } catch (\Exception $e) { | |
| $filesystem->remove($imagePath); | |
| $filesystem->remove($thumbnailPath); | |
| $this->orm->remove($image); | |
| $this->orm->flush(); | |
| throw new \Exception($e->getMessage(), $e->getCode()); | |
| } | |
| $this->cacheService->clearNamespace('image'); | |
| return $image; | |
| } | |
| /** | |
| * Remove image (files and entity) | |
| * | |
| * @param LocalImage $image | |
| * | |
| * @return boolean | |
| */ | |
| public function remove(LocalImage $image) | |
| { | |
| $filesystem = new Filesystem(); | |
| if (file_exists($image->getPath())) { | |
| $filesystem->remove($image->getPath()); | |
| } | |
| if (file_exists($image->getThumbnailPath())) { | |
| unlink($this->config['thumbnail_path'] . $image->getThumbnailPath(true)); | |
| } | |
| $articleImages = $this->orm->getRepository('Newscoop\Image\ArticleImage') | |
| ->getArticleImagesForImage($image) | |
| ->getResult(); | |
| foreach ($articleImages as $articleImage) { | |
| \ArticleImage::RemoveImageTagsFromArticleText($articleImage->getArticleNumber(), $articleImage->getNumber()); | |
| $this->orm->remove($articleImage); | |
| } | |
| $this->orm->remove($image); | |
| $this->orm->flush(); | |
| $this->cacheService->clearNamespace('article_image'); | |
| $this->cacheService->clearNamespace('image'); | |
| return true; | |
| } | |
| /** | |
| * Save image | |
| * | |
| * @param array $info | |
| * | |
| * @return string | |
| */ | |
| public function save(array $info) | |
| { | |
| if (!in_array($info['type'], $this->supportedTypes)) { | |
| throw new \InvalidArgumentException("Unsupported image type '$info[type]'."); | |
| } | |
| $name = sha1_file($info['tmp_name']) . '.' . array_pop(explode('.', $info['name'])); | |
| if (!file_exists(APPLICATION_PATH . "/../images/$name")) { | |
| rename($info['tmp_name'], APPLICATION_PATH . "/../images/$name"); | |
| } | |
| return $name; | |
| } | |
| /** | |
| * Get image src | |
| * | |
| * @param string $image | |
| * @param int $width | |
| * @param int $height | |
| * @param string $specs | |
| * | |
| * @return string | |
| */ | |
| public function getSrc($image, $width, $height, $specs = 'fit') | |
| { | |
| return implode('/', array( | |
| "{$width}x{$height}", | |
| $specs, | |
| $this->encodePath($image), | |
| )); | |
| } | |
| /** | |
| * Generate image for given src | |
| * | |
| * @param string $src | |
| * | |
| * @return void | |
| */ | |
| public function generateFromSrc($src) | |
| { | |
| $matches = array(); | |
| if (!preg_match('#^([0-9]+)x([0-9]+)/([_a-z0-9]+)/([-_.:~%|a-zA-Z0-9]+)$#', $src, $matches)) { | |
| return; | |
| } | |
| list(, $width, $height, $specs, $imagePath) = $matches; | |
| $destFolder = rtrim($this->config['cache_path'], '/') . '/' . dirname(ltrim($src, './')); | |
| if (!realpath($destFolder)) { | |
| mkdir($destFolder, 0755, true); | |
| } | |
| if (!is_dir($destFolder)) { | |
| throw new \RuntimeException("Can't create folder '$destFolder'."); | |
| } | |
| $rendition = new Rendition($width, $height, $specs); | |
| $image = $rendition->generateImage($this->decodePath($imagePath)); | |
| $image->save($destFolder . '/' . $imagePath, array( | |
| 'quality' => 90, //from 0 to 100 | |
| )); | |
| return $image; | |
| } | |
| /** | |
| * Generate file path for thumbnail | |
| * | |
| * @param int $imageId | |
| * @param string $extension | |
| * @param boolean $olnyFileName | |
| * | |
| * @return string | |
| */ | |
| private function generateThumbnailPath($imageId, $extension, $olnyFileName = false) | |
| { | |
| if ($olnyFileName) { | |
| return $this->config['thumbnail_prefix'] . sprintf('%09d', $imageId) .'.'. $extension; | |
| } | |
| return $this->config['thumbnail_path'] . $this->config['thumbnail_prefix'] . sprintf('%09d', $imageId) .'.'. $extension; | |
| } | |
| /** | |
| * Generate file path for image | |
| * | |
| * @param int $imageId | |
| * @param string $extension | |
| * @param boolean $olnyFileName | |
| * | |
| * @return string | |
| */ | |
| private function generateImagePath($imageId, $extension, $olnyFileName = false) | |
| { | |
| if ($olnyFileName) { | |
| return $this->config['image_prefix'] . sprintf('%09d', $imageId) .'.'. $extension; | |
| } | |
| return $this->config['image_path'] . $this->config['image_prefix'] . sprintf('%09d', $imageId) .'.'. $extension; | |
| } | |
| /** | |
| * Fill image with custom/default arttributes | |
| * | |
| * @param LocalImage $image | |
| * @param array $attributes | |
| * | |
| * @return LocalImage | |
| */ | |
| public function fillImage($image, $attributes) | |
| { | |
| $attributes = array_merge(array( | |
| 'date' => date('Y-m-d'), | |
| 'content_type' => 'image/jpeg', | |
| 'user' => null, | |
| 'updated' => new \DateTime(), | |
| 'status' => 'unapproved', | |
| 'source' => 'local', | |
| 'description' => '' | |
| ), $attributes); | |
| if (isset($attributes['description'])) { $image->setDescription($attributes['description']); } else { $image->setDescription(null); } | |
| if (isset($attributes['photographer'])) { $image->setPhotographer($attributes['photographer']); } else { $image->setPhotographer(null); } | |
| if (isset($attributes['photographer_url'])) { $image->setPhotographerUrl($attributes['photographer_url']); } else { $image->setPhotographerUrl(null); } | |
| if (isset($attributes['place'])) { $image->setPlace($attributes['place']); } else { $image->setPlace(null); } | |
| $image->setDate($attributes['date']); | |
| $image->setContentType($attributes['content_type']); | |
| $image->setUser($attributes['user']); | |
| $image->setUpdated($attributes['updated']); | |
| $image->setSource($attributes['source']); | |
| if (isset($attributes['url'])) { $image->setUrl($attributes['url']); } | |
| if ($image->getUser() && $image->getUser()->isAdmin() == true) { | |
| $image->setStatus('approved'); | |
| } else { | |
| $image->setStatus($attributes['status']); | |
| } | |
| return $image; | |
| } | |
| /** | |
| * Save article image | |
| * | |
| * @param Newscoop\Image\ArticleImage $articleImage | |
| * @param array $values | |
| * @return void | |
| */ | |
| public function saveArticleImage(ArticleImage $articleImage, array $values) | |
| { | |
| $language = $this->orm->getReference('Newscoop\Entity\Language', $values['language']); | |
| $articleImage->setNumber($values['number']); | |
| $articleImage->setCaption($values['caption'], $language); | |
| $this->orm->flush(); | |
| } | |
| /** | |
| * Add article image | |
| * | |
| * @param int $articleNumber | |
| * @param Newscoop\Image\LocalImage $image | |
| * @param bool $defaultImage | |
| * | |
| * @return Newscoop\Image\ArticleImage | |
| */ | |
| public function addArticleImage($articleNumber, LocalImage $image, $defaultImage = false) | |
| { | |
| if ($image->getId() === null) { | |
| $this->orm->persist($image); | |
| $this->orm->flush($image); | |
| } | |
| if ($this->getArticleImage($articleNumber, $image->getId())) { | |
| throw new ResourcesConflictException("Image already attached to article", 409); | |
| } | |
| $imagesCount = $this->getArticleImagesCount($articleNumber); | |
| $articleImage = new ArticleImage( | |
| $articleNumber, | |
| $image, | |
| $defaultImage || $imagesCount === 0, | |
| $imagesCount+1 | |
| ); | |
| $this->orm->persist($articleImage); | |
| $this->orm->flush($articleImage); | |
| return $articleImage; | |
| } | |
| /** | |
| * Remove image from article | |
| * | |
| * @param ArticleImage $articleImage | |
| */ | |
| public function removeArticleImage(ArticleImage $articleImage) | |
| { | |
| \ArticleImage::RemoveImageTagsFromArticleText($articleImage->getArticleNumber(), $articleImage->getNumber()); | |
| $this->orm->remove($articleImage); | |
| $this->orm->flush(); | |
| } | |
| /** | |
| * Get article image | |
| * | |
| * @param int $articleNumber | |
| * @param int $imageId | |
| * | |
| * @return Newscoop\Image\ArticleImage | |
| */ | |
| public function getArticleImage($articleNumber, $imageId) | |
| { | |
| return $this->orm->getRepository('Newscoop\Image\ArticleImage') | |
| ->findOneBy(array( | |
| 'articleNumber' => (int) $articleNumber, | |
| 'image' => $imageId, | |
| )); | |
| } | |
| /** | |
| * Find images by article | |
| * | |
| * @param int $articleNumber | |
| * | |
| * @return array | |
| */ | |
| public function findByArticle($articleNumber) | |
| { | |
| $this->updateSchema($articleNumber); | |
| $images = $this->orm->getRepository('Newscoop\Image\ArticleImage') | |
| ->findBy(array( | |
| 'articleNumber' => (int) $articleNumber, | |
| ), array('number' => 'asc')); | |
| $hasDefault = array_reduce($images, function ($hasDefault, $image) { | |
| return $hasDefault || $image->isDefault(); | |
| }, false); | |
| if (!empty($images) && $hasDefault === false) { | |
| $images[0]->setIsDefault(true); | |
| } | |
| return $images; | |
| } | |
| /** | |
| * Set default article image | |
| * | |
| * @param int $articleNumber | |
| * @param ImageInterface $image | |
| * | |
| * @return void | |
| */ | |
| public function setDefaultArticleImage($articleNumber, ArticleImage $image) | |
| { | |
| $query = $this->orm->createQuery('UPDATE Newscoop\Image\ArticleImage i SET i.isDefault = 0 WHERE i.articleNumber = :articleNumber'); | |
| $query->setParameter('articleNumber', $articleNumber) | |
| ->execute(); | |
| $image->setIsDefault(true); | |
| $this->orm->flush($image); | |
| $this->orm->clear(); | |
| } | |
| /** | |
| * Get default article image | |
| * | |
| * @param int $articleNumber | |
| * | |
| * @return Newscoop\Image\ArticleImage | |
| */ | |
| public function getDefaultArticleImage($articleNumber) | |
| { | |
| $image = $this->orm->getRepository('Newscoop\Image\ArticleImage') | |
| ->findOneBy(array( | |
| 'articleNumber' => (int) $articleNumber, | |
| 'isDefault' => true, | |
| )); | |
| if ($image === null) { | |
| $image = $this->orm->getRepository('Newscoop\Image\ArticleImage')->findOneBy( | |
| array('articleNumber' => (int) $articleNumber), | |
| array('number' => 'asc') | |
| ); | |
| if ($image !== null) { | |
| $image->setIsDefault(true); | |
| $this->orm->flush($image); | |
| } | |
| } | |
| return $image; | |
| } | |
| /** | |
| * Get thumbnail for given image | |
| * | |
| * @param string $image | |
| * @param int $width | |
| * @param int $height | |
| * @param string $specs | |
| * | |
| * @return mixed | |
| */ | |
| public function thumbnail($image, $width, $height, $specs) | |
| { | |
| if (is_string($image)) { | |
| $image = new \Newscoop\Image\LocalImage($image); | |
| } | |
| return $this->getThumbnail(new \Newscoop\Image\Rendition($width, $height, $specs), $image); | |
| } | |
| /** | |
| * Get thumbnail for given image and rendition | |
| * | |
| * @param Newscoop\Image\Rendition $rendition | |
| * @param Newscoop\Image\ImageInterface $image | |
| * | |
| * @return Newscoop\Image\Thumbnail | |
| */ | |
| public function getThumbnail(Rendition $rendition, ImageInterface $image) | |
| { | |
| return $rendition->getThumbnail($image, $this); | |
| } | |
| /** | |
| * Get count of article images | |
| * | |
| * @param int $articleNumber | |
| * | |
| * @return int | |
| */ | |
| public function getArticleImagesCount($articleNumber) | |
| { | |
| $query = $this->orm->getRepository('Newscoop\Image\ArticleImage') | |
| ->createQueryBuilder('i') | |
| ->select('MAX(i.number)') | |
| ->where('i.articleNumber = :articleNumber') | |
| ->getQuery(); | |
| return $query | |
| ->setParameter('articleNumber', $articleNumber) | |
| ->getSingleScalarResult(); | |
| } | |
| /** | |
| * Find image | |
| * | |
| * @param int $id | |
| * | |
| * @return Newscoop\Image\LocalImage | |
| */ | |
| public function find($id) | |
| { | |
| return $this->orm->getRepository('Newscoop\Image\LocalImage') | |
| ->find($id); | |
| } | |
| /** | |
| * Find images by a set of criteria | |
| * | |
| * @param array $criteria | |
| * @param array $orderBy | |
| * @param int $limit | |
| * @param int $offset | |
| * | |
| * @return array | |
| */ | |
| public function findBy(array $criteria, $orderBy = null, $limit = 25, $offset = 0) | |
| { | |
| return $this->orm->getRepository('Newscoop\Image\LocalImage') | |
| ->findBy($criteria, $orderBy, $limit, $offset); | |
| } | |
| /** | |
| * Get count of images for a set of criteria | |
| * | |
| * @param array $criteria | |
| * | |
| * @return int | |
| */ | |
| public function getCountBy(array $criteria) | |
| { | |
| $qb = $this->orm->getRepository('Newscoop\Image\LocalImage') | |
| ->createQueryBuilder('i') | |
| ->select('COUNT(i)'); | |
| if (isset($criteria['source']) && is_array($criteria['source']) && (!empty($criteria['source']))) { | |
| $sourceCases = array(); | |
| foreach ($criteria['source'] as $oneSource) { | |
| $sourceCases[] = $qb->expr()->literal($oneSource); | |
| } | |
| $qb->andwhere('i.source IN (:source)'); | |
| $qb->setParameter('source', $sourceCases); | |
| } | |
| return (int) $qb->getQuery() | |
| ->getSingleScalarResult(); | |
| } | |
| /** | |
| * Encode path | |
| * | |
| * @param string $path | |
| * | |
| * @return string | |
| */ | |
| private function encodePath($path) | |
| { | |
| return rawurlencode(str_replace('/', '|', $path)); | |
| } | |
| /** | |
| * Decode path | |
| * | |
| * @param string $path | |
| * | |
| * @return string | |
| */ | |
| private function decodePath($path) | |
| { | |
| return str_replace('|', '/', rawurldecode($path)); | |
| } | |
| /** | |
| * Get user image | |
| * | |
| * @param Newscoop\Entity\User $user | |
| * @param int $width | |
| * @param int $height | |
| * @return string | |
| */ | |
| public function getUserImage(User $user, $width = 65, $height = 65) | |
| { | |
| if ($user->getImage() !== null) { | |
| return $this->getSrc('images/' . $user->getImage(), $width, $height, 'crop'); | |
| } | |
| return null; | |
| } | |
| /** | |
| * Update schema if needed | |
| * | |
| * @param integer $articleNumber | |
| * | |
| * @return void | |
| */ | |
| private function updateSchema($articleNumber) | |
| { | |
| try { | |
| $this->orm->getRepository('Newscoop\Image\ArticleImage') | |
| ->findOneBy(array( | |
| 'articleNumber' => (int) $articleNumber, | |
| )); | |
| } catch (\Exception $e) { | |
| if ($e->getCode() === '42S22') { | |
| $this->orm->getConnection()->exec('ALTER TABLE ArticleImages ADD is_default INT(1) DEFAULT NULL'); | |
| } | |
| } | |
| } | |
| /** | |
| * Gets path of local images | |
| * | |
| * @return string | |
| */ | |
| public function getImagePath() | |
| { | |
| return $this->config['image_path']; | |
| } | |
| /** | |
| * Return true if the image is being used by an article. | |
| * | |
| * @param LocalImage $image Local image | |
| * | |
| * @return boolean | |
| */ | |
| public function inUse($image) | |
| { | |
| $imageArticle = $this->orm->getRepository('Newscoop\Image\ArticleImage')->findOneBy(array( | |
| 'image' => $image, | |
| )); | |
| if ($imageArticle) { | |
| $imagesCount = $this->orm->getRepository('Newscoop\Entity\Article') | |
| ->createQueryBuilder('a') | |
| ->select('count(a)') | |
| ->where('number = :articleNumber') | |
| ->andWhere('images = :image') | |
| ->setParameter('image', $imageArticle) | |
| ->setParameter('articleNumber', $imageArticle->getArticleNumber()) | |
| ->getQuery() | |
| ->getSingleScalarResult(); | |
| if ((int) $imagesCount > 0) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| /** | |
| * Get image caption | |
| * | |
| * @param int $image | |
| * @param int $articleNumber | |
| * @param int $languageId | |
| * | |
| * @return string | |
| */ | |
| public function getCaption(\Newscoop\Image\LocalImage $image, $articleNumber, $languageId) | |
| { | |
| $caption = $this->getArticleImageCaption($image->getId(), $articleNumber, $languageId); | |
| if (!empty($caption)) { | |
| return $caption; | |
| } | |
| return $image->getDescription(); | |
| } | |
| /** | |
| * Get article specific image caption | |
| * | |
| * @param int $imageId | |
| * @param int $articleNumber | |
| * @param int $languageId | |
| * | |
| * @return string | |
| */ | |
| public function getArticleImageCaption($imageId, $articleNumber, $languageId) | |
| { | |
| $query = $this->orm->getRepository('Newscoop\Image\ArticleImageCaption')->createQueryBuilder('c') | |
| ->select('c.caption') | |
| ->where('c.articleNumber = :article') | |
| ->andWhere('c.image = :image') | |
| ->andWhere('c.languageId = :language') | |
| ->getQuery(); | |
| $query->setParameters(array( | |
| 'article' => $articleNumber, | |
| 'image' => $imageId, | |
| 'language' => $languageId, | |
| )); | |
| try { | |
| return $query->getSingleScalarResult(); | |
| } catch (NoResultException $e) {} | |
| } | |
| /** | |
| * Calculates dimensions of resized image. | |
| * @param mixed source width | |
| * @param mixed source height | |
| * @param mixed width in pixels or percent | |
| * @param mixed height in pixels or percent | |
| * @param int flags | |
| * @return array | |
| */ | |
| public static function calculateSize($srcWidth, $srcHeight, $newWidth, $newHeight, $flags = self::FIT) | |
| { | |
| if (substr($newWidth, -1) === '%') { | |
| $newWidth = round($srcWidth / 100 * abs($newWidth)); | |
| $flags |= self::ENLARGE; | |
| $percents = TRUE; | |
| } else { | |
| $newWidth = (int) abs($newWidth); | |
| } | |
| if (substr($newHeight, -1) === '%') { | |
| $newHeight = round($srcHeight / 100 * abs($newHeight)); | |
| $flags |= empty($percents) ? self::ENLARGE : self::STRETCH; | |
| } else { | |
| $newHeight = (int) abs($newHeight); | |
| } | |
| if ($flags & self::STRETCH) { // non-proportional | |
| if (empty($newWidth) || empty($newHeight)) { | |
| throw new \InvalidArgumentException('For stretching must be both width and height specified.'); | |
| } | |
| if (($flags & self::ENLARGE) === 0) { | |
| $newWidth = round($srcWidth * min(1, $newWidth / $srcWidth)); | |
| $newHeight = round($srcHeight * min(1, $newHeight / $srcHeight)); | |
| } | |
| } else { // proportional | |
| if (empty($newWidth) && empty($newHeight)) { | |
| throw new \InvalidArgumentException('At least width or height must be specified.'); | |
| } | |
| $scale = array(); | |
| if ($newWidth > 0) { // fit width | |
| $scale[] = $newWidth / $srcWidth; | |
| } | |
| if ($newHeight > 0) { // fit height | |
| $scale[] = $newHeight / $srcHeight; | |
| } | |
| if ($flags & self::FILL) { | |
| $scale = array(max($scale)); | |
| } | |
| if (($flags & self::ENLARGE) === 0) { | |
| $scale[] = 1; | |
| } | |
| $scale = min($scale); | |
| $newWidth = round($srcWidth * $scale); | |
| $newHeight = round($srcHeight * $scale); | |
| } | |
| return array(max((int) $newWidth, 1), max((int) $newHeight, 1)); | |
| } | |
| /** | |
| * Calculates dimensions of cutout in image. | |
| * @param mixed source width | |
| * @param mixed source height | |
| * @param mixed x-offset in pixels or percent | |
| * @param mixed y-offset in pixels or percent | |
| * @param mixed width in pixels or percent | |
| * @param mixed height in pixels or percent | |
| * @return array | |
| */ | |
| public static function calculateCutout($srcWidth, $srcHeight, $left, $top, $newWidth, $newHeight) | |
| { | |
| if (substr($newWidth, -1) === '%') { | |
| $newWidth = round($srcWidth / 100 * $newWidth); | |
| } | |
| if (substr($newHeight, -1) === '%') { | |
| $newHeight = round($srcHeight / 100 * $newHeight); | |
| } | |
| if (substr($left, -1) === '%') { | |
| $left = round(($srcWidth - $newWidth) / 100 * $left); | |
| } | |
| if (substr($top, -1) === '%') { | |
| $top = round(($srcHeight - $newHeight) / 100 * $top); | |
| } | |
| if ($left < 0) { | |
| $newWidth += $left; $left = 0; | |
| } | |
| if ($top < 0) { | |
| $newHeight += $top; $top = 0; | |
| } | |
| $newWidth = min((int) $newWidth, $srcWidth - $left); | |
| $newHeight = min((int) $newHeight, $srcHeight - $top); | |
| return array($left, $top, $newWidth, $newHeight); | |
| } | |
| } |