dedijk4/vendor/gregwar/image/Gregwar/Image/Image.php

797 lines
19 KiB
PHP

<?php
namespace Gregwar\Image;
use Gregwar\Cache\CacheInterface;
use Gregwar\Image\Adapter\AdapterInterface;
use Gregwar\Image\Exceptions\GenerationError;
/**
* Images handling class.
*
* @author Gregwar <g.passault@gmail.com>
*
* @method Image saveGif($file)
* @method Image savePng($file)
* @method Image saveJpeg($file, $quality)
* @method Image resize($width = null, $height = null, $background = 'transparent', $force = false, $rescale = false, $crop = false)
* @method Image forceResize($width = null, $height = null, $background = 'transparent')
* @method Image scaleResize($width = null, $height = null, $background = 'transparent', $crop = false)
* @method Image cropResize($width = null, $height = null, $background=0xffffff)
* @method Image scale($width = null, $height = null, $background=0xffffff, $crop = false)
* @method Image ($width = null, $height = null, $background = 0xffffff, $force = false, $rescale = false, $crop = false)
* @method Image crop($x, $y, $width, $height)
* @method Image enableProgressive()
* @method Image force($width = null, $height = null, $background = 0xffffff)
* @method Image zoomCrop($width, $height, $background = 0xffffff, $xPos, $yPos)
* @method Image fillBackground($background = 0xffffff)
* @method Image negate()
* @method Image brightness($brightness)
* @method Image contrast($contrast)
* @method Image grayscale()
* @method Image emboss()
* @method Image smooth($p)
* @method Image sharp()
* @method Image edge()
* @method Image colorize($red, $green, $blue)
* @method Image sepia()
* @method Image merge(Image $other, $x = 0, $y = 0, $width = null, $height = null)
* @method Image rotate($angle, $background = 0xffffff)
* @method Image fill($color = 0xffffff, $x = 0, $y = 0)
* @method Image write($font, $text, $x = 0, $y = 0, $size = 12, $angle = 0, $color = 0x000000, $align = 'left')
* @method Image rectangle($x1, $y1, $x2, $y2, $color, $filled = false)
* @method Image roundedRectangle($x1, $y1, $x2, $y2, $radius, $color, $filled = false)
* @method Image line($x1, $y1, $x2, $y2, $color = 0x000000)
* @method Image ellipse($cx, $cy, $width, $height, $color = 0x000000, $filled = false)
* @method Image circle($cx, $cy, $r, $color = 0x000000, $filled = false)
* @method Image polygon(array $points, $color, $filled = false)
* @method Image flip($flipVertical, $flipHorizontal)
*/
class Image
{
/**
* Directory to use for file caching.
*/
protected $cacheDir = 'cache/images';
/**
* Directory cache mode.
*/
protected $cacheMode = null;
/**
* Internal adapter.
*
* @var AdapterInterface
*/
protected $adapter = null;
/**
* Pretty name for the image.
*/
protected $prettyName = '';
protected $prettyPrefix;
/**
* Transformations hash.
*/
protected $hash = null;
/**
* The image source.
*/
protected $source = null;
/**
* Force image caching, even if there is no operation applied.
*/
protected $forceCache = true;
/**
* Supported types.
*/
public static $types = array(
'jpg' => 'jpeg',
'jpeg' => 'jpeg',
'webp' => 'webp',
'png' => 'png',
'gif' => 'gif',
);
/**
* Fallback image.
*/
protected $fallback;
/**
* Use fallback image.
*/
protected $useFallbackImage = true;
/**
* Cache system.
*
* @var \Gregwar\Cache\CacheInterface
*/
protected $cache;
/**
* Get the cache system.
*
* @return \Gregwar\Cache\CacheInterface
*/
public function getCacheSystem()
{
if (is_null($this->cache)) {
$this->cache = new \Gregwar\Cache\Cache();
$this->cache->setCacheDirectory($this->cacheDir);
}
return $this->cache;
}
/**
* Set the cache system.
*
* @param \Gregwar\Cache\CacheInterface $cache
*/
public function setCacheSystem(CacheInterface $cache)
{
$this->cache = $cache;
}
/**
* Change the caching directory.
*/
public function setCacheDir($cacheDir)
{
$this->getCacheSystem()->setCacheDirectory($cacheDir);
return $this;
}
/**
* @param int $dirMode
*/
public function setCacheDirMode($dirMode)
{
$this->cache->setDirectoryMode($dirMode);
}
/**
* Enable or disable to force cache even if the file is unchanged.
*/
public function setForceCache($forceCache = true)
{
$this->forceCache = $forceCache;
return $this;
}
/**
* The actual cache dir.
*/
public function setActualCacheDir($actualCacheDir)
{
$this->getCacheSystem()->setActualCacheDirectory($actualCacheDir);
return $this;
}
/**
* Sets the pretty name of the image.
*/
public function setPrettyName($name, $prefix = true)
{
if (empty($name)) {
return $this;
}
$this->prettyName = $this->urlize($name);
$this->prettyPrefix = $prefix;
return $this;
}
/**
* Urlizes the prettyName.
*/
protected function urlize($name)
{
$transliterator = '\Behat\Transliterator\Transliterator';
if (class_exists($transliterator)) {
$name = $transliterator::transliterate($name);
$name = $transliterator::urlize($name);
} else {
$name = strtolower($name);
$name = str_replace(' ', '-', $name);
$name = preg_replace('/([^a-z0-9\-]+)/m', '', $name);
}
return $name;
}
/**
* Operations array.
*/
protected $operations = array();
public function __construct($originalFile = null, $width = null, $height = null)
{
$this->setFallback(null);
if ($originalFile) {
$this->source = new Source\File($originalFile);
} else {
$this->source = new Source\Create($width, $height);
}
}
/**
* Sets the image data.
*/
public function setData($data)
{
$this->source = new Source\Data($data);
}
/**
* Sets the resource.
*/
public function setResource($resource)
{
$this->source = new Source\Resource($resource);
}
/**
* Use the fallback image or not.
*/
public function useFallback($useFallbackImage = true)
{
$this->useFallbackImage = $useFallbackImage;
return $this;
}
/**
* Sets the fallback image to use.
*/
public function setFallback($fallback = null)
{
if ($fallback === null) {
$this->fallback = __DIR__.'/images/error.jpg';
} else {
$this->fallback = $fallback;
}
return $this;
}
/**
* Gets the fallack image path.
*/
public function getFallback()
{
return $this->fallback;
}
/**
* Gets the fallback into the cache dir.
*/
public function getCacheFallback()
{
$fallback = $this->fallback;
return $this->getCacheSystem()->getOrCreateFile('fallback.jpg', array(), function ($target) use ($fallback) {
copy($fallback, $target);
});
}
/**
* @return AdapterInterface
*/
public function getAdapter()
{
if (null === $this->adapter) {
// Defaults to GD
$this->setAdapter('gd');
}
return $this->adapter;
}
public function setAdapter($adapter)
{
if ($adapter instanceof Adapter\Adapter) {
$this->adapter = $adapter;
} else {
if (is_string($adapter)) {
$adapter = strtolower($adapter);
switch ($adapter) {
case 'gd':
$this->adapter = new Adapter\GD();
break;
case 'imagemagick':
case 'imagick':
$this->adapter = new Adapter\Imagick();
break;
default:
throw new \Exception('Unknown adapter: '.$adapter);
break;
}
} else {
throw new \Exception('Unable to load the given adapter (not string or Adapter)');
}
}
$this->adapter->setSource($this->source);
}
/**
* Get the file path.
*
* @return mixed a string with the filen name, null if the image
* does not depends on a file
*/
public function getFilePath()
{
if ($this->source instanceof Source\File) {
return $this->source->getFile();
} else {
return;
}
}
/**
* Defines the file only after instantiation.
*
* @param string $originalFile the file path
*/
public function fromFile($originalFile)
{
$this->source = new Source\File($originalFile);
return $this;
}
/**
* Tells if the image is correct.
*/
public function correct()
{
return $this->source->correct();
}
/**
* Guess the file type.
*/
public function guessType()
{
return $this->source->guessType();
}
/**
* Adds an operation.
*/
protected function addOperation($method, $args)
{
$this->operations[] = array($method, $args);
}
/**
* Generic function.
*/
public function __call($methodName, $args)
{
$adapter = $this->getAdapter();
$reflection = new \ReflectionClass(get_class($adapter));
if ($reflection->hasMethod($methodName)) {
$method = $reflection->getMethod($methodName);
if ($method->getNumberOfRequiredParameters() > count($args)) {
throw new \InvalidArgumentException('Not enough arguments given for '.$methodName);
}
$this->addOperation($methodName, $args);
return $this;
}
throw new \BadFunctionCallException('Invalid method: '.$methodName);
}
/**
* Serialization of operations.
*/
public function serializeOperations()
{
$datas = array();
foreach ($this->operations as $operation) {
$method = $operation[0];
$args = $operation[1];
foreach ($args as &$arg) {
if ($arg instanceof self) {
$arg = $arg->getHash();
}
}
$datas[] = array($method, $args);
}
return serialize($datas);
}
/**
* Generates the hash.
*/
public function generateHash($type = 'guess', $quality = 80)
{
$inputInfos = $this->source->getInfos();
$datas = array(
$inputInfos,
$this->serializeOperations(),
$type,
$quality,
);
$this->hash = sha1(serialize($datas));
}
/**
* Gets the hash.
*/
public function getHash($type = 'guess', $quality = 80)
{
if (null === $this->hash) {
$this->generateHash($type, $quality);
}
return $this->hash;
}
/**
* Gets the cache file name and generate it if it does not exists.
* Note that if it exists, all the image computation process will
* not be done.
*
* @param string $type the image type
* @param int $quality the quality (for JPEG)
*/
public function cacheFile($type = 'jpg', $quality = 80, $actual = false)
{
if ($type == 'guess') {
$type = $this->guessType();
}
if (!count($this->operations) && $type == $this->guessType() && !$this->forceCache) {
return $this->getFilename($this->getFilePath());
}
// Computes the hash
$this->hash = $this->getHash($type, $quality);
// Generates the cache file
$cacheFile = '';
if (!$this->prettyName || $this->prettyPrefix) {
$cacheFile .= $this->hash;
}
if ($this->prettyPrefix) {
$cacheFile .= '-';
}
if ($this->prettyName) {
$cacheFile .= $this->prettyName;
}
$cacheFile .= '.'.$type;
// If the files does not exists, save it
$image = $this;
// Target file should be younger than all the current image
// dependencies
$conditions = array(
'younger-than' => $this->getDependencies(),
);
// The generating function
$generate = function ($target) use ($image, $type, $quality) {
$result = $image->save($target, $type, $quality);
if ($result != $target) {
throw new GenerationError($result);
}
};
// Asking the cache for the cacheFile
try {
$file = $this->getCacheSystem()->getOrCreateFile($cacheFile, $conditions, $generate, $actual);
} catch (GenerationError $e) {
$file = $e->getNewFile();
}
// Nulling the resource
$this->getAdapter()->setSource(new Source\File($file));
$this->getAdapter()->deinit();
if ($actual) {
return $file;
} else {
return $this->getFilename($file);
}
}
/**
* Get cache data (to render the image).
*
* @param string $type the image type
* @param int $quality the quality (for JPEG)
*/
public function cacheData($type = 'jpg', $quality = 80)
{
return file_get_contents($this->cacheFile($type, $quality));
}
/**
* Hook to helps to extends and enhance this class.
*/
protected function getFilename($filename)
{
return $filename;
}
/**
* Generates and output a jpeg cached file.
*/
public function jpeg($quality = 80)
{
return $this->cacheFile('jpg', $quality);
}
/**
* Generates and output a gif cached file.
*/
public function gif()
{
return $this->cacheFile('gif');
}
/**
* Generates and output a png cached file.
*/
public function png()
{
return $this->cacheFile('png');
}
/**
* Generates and output a png cached file.
*/
public function webp()
{
return $this->cacheFile('webp');
}
/**
* Generates and output an image using the same type as input.
*/
public function guess($quality = 80)
{
return $this->cacheFile('guess', $quality);
}
/**
* Get all the files that this image depends on.
*
* @return string[] this is an array of strings containing all the files that the
* current Image depends on
*/
public function getDependencies()
{
$dependencies = array();
$file = $this->getFilePath();
if ($file) {
$dependencies[] = $file;
}
foreach ($this->operations as $operation) {
foreach ($operation[1] as $argument) {
if ($argument instanceof self) {
$dependencies = array_merge($dependencies, $argument->getDependencies());
}
}
}
return $dependencies;
}
/**
* Applies the operations.
*/
public function applyOperations()
{
// Renders the effects
foreach ($this->operations as $operation) {
call_user_func_array(array($this->adapter, $operation[0]), $operation[1]);
}
}
/**
* Initialize the adapter.
*/
public function init()
{
$this->getAdapter()->init();
}
/**
* Save the file to a given output.
*/
public function save($file, $type = 'guess', $quality = 80)
{
if ($file) {
$directory = dirname($file);
if (!is_dir($directory)) {
@mkdir($directory, 0777, true);
}
}
if (is_int($type)) {
$quality = $type;
$type = 'jpeg';
}
if ($type == 'guess') {
$type = $this->guessType();
}
if (!isset(self::$types[$type])) {
throw new \InvalidArgumentException('Given type ('.$type.') is not valid');
}
$type = self::$types[$type];
try {
$this->init();
$this->applyOperations();
$success = false;
if (null == $file) {
ob_start();
}
if ($type == 'jpeg') {
$success = $this->getAdapter()->saveJpeg($file, $quality);
}
if ($type == 'gif') {
$success = $this->getAdapter()->saveGif($file);
}
if ($type == 'png') {
$success = $this->getAdapter()->savePng($file);
}
if ($type == 'webp') {
$success = $this->getAdapter()->saveWebP($file, $quality);
}
if (!$success) {
return false;
}
return null === $file ? ob_get_clean() : $file;
} catch (\Exception $e) {
if ($this->useFallbackImage) {
return null === $file ? file_get_contents($this->fallback) : $this->getCacheFallback();
} else {
throw $e;
}
}
}
/**
* Get the contents of the image.
*/
public function get($type = 'guess', $quality = 80)
{
return $this->save(null, $type, $quality);
}
/* Image API */
/**
* Image width.
*/
public function width()
{
return $this->getAdapter()->width();
}
/**
* Image height.
*/
public function height()
{
return $this->getAdapter()->height();
}
/**
* Tostring defaults to jpeg.
*/
public function __toString()
{
return $this->guess();
}
/**
* Returning basic html code for this image.
*/
public function html($title = '', $type = 'jpg', $quality = 80)
{
return '<img title="'.$title.'" src="'.$this->cacheFile($type, $quality).'" />';
}
/**
* Returns the Base64 inlinable representation.
*/
public function inline($type = 'jpg', $quality = 80)
{
$mime = $type;
if ($mime == 'jpg') {
$mime = 'jpeg';
}
return 'data:image/'.$mime.';base64,'.base64_encode(file_get_contents($this->cacheFile($type, $quality, true)));
}
/**
* Creates an instance, usefull for one-line chaining.
*/
public static function open($file = '')
{
return new static($file);
}
/**
* Creates an instance of a new resource.
*/
public static function create($width, $height)
{
return new static(null, $width, $height);
}
/**
* Creates an instance of image from its data.
*/
public static function fromData($data)
{
$image = new static();
$image->setData($data);
return $image;
}
/**
* Creates an instance of image from resource.
*/
public static function fromResource($resource)
{
$image = new static();
$image->setResource($resource);
return $image;
}
}