vendor/gregwar/image/Gregwar/Image/Image.php line 620

Open in your IDE?
  1. <?php
  2. namespace Gregwar\Image;
  3. use Gregwar\Cache\CacheInterface;
  4. use Gregwar\Image\Adapter\AdapterInterface;
  5. use Gregwar\Image\Exceptions\GenerationError;
  6. /**
  7.  * Images handling class.
  8.  *
  9.  * @author Gregwar <g.passault@gmail.com>
  10.  *
  11.  * @method Image saveGif($file)
  12.  * @method Image savePng($file)
  13.  * @method Image saveJpeg($file, $quality)
  14.  * @method Image resize($width = null, $height = null, $background = 'transparent', $force = false, $rescale = false, $crop = false)
  15.  * @method Image forceResize($width = null, $height = null, $background = 'transparent')
  16.  * @method Image scaleResize($width = null, $height = null, $background = 'transparent', $crop = false)
  17.  * @method Image cropResize($width = null, $height = null, $background=0xffffff)
  18.  * @method Image scale($width = null, $height = null, $background=0xffffff, $crop = false)
  19.  * @method Image ($width = null, $height = null, $background = 0xffffff, $force = false, $rescale = false, $crop = false)
  20.  * @method Image crop($x, $y, $width, $height)
  21.  * @method Image enableProgressive()
  22.  * @method Image force($width = null, $height = null, $background = 0xffffff)
  23.  * @method Image zoomCrop($width, $height, $background = 0xffffff, $xPos, $yPos)
  24.  * @method Image fillBackground($background = 0xffffff)
  25.  * @method Image negate()
  26.  * @method Image brightness($brightness)
  27.  * @method Image contrast($contrast)
  28.  * @method Image grayscale()
  29.  * @method Image emboss()
  30.  * @method Image smooth($p)
  31.  * @method Image sharp()
  32.  * @method Image edge()
  33.  * @method Image colorize($red, $green, $blue)
  34.  * @method Image sepia()
  35.  * @method Image merge(Image $other, $x = 0, $y = 0, $width = null, $height = null)
  36.  * @method Image rotate($angle, $background = 0xffffff)
  37.  * @method Image fill($color = 0xffffff, $x = 0, $y = 0)
  38.  * @method Image write($font, $text, $x = 0, $y = 0, $size = 12, $angle = 0, $color = 0x000000, $align = 'left')
  39.  * @method Image rectangle($x1, $y1, $x2, $y2, $color, $filled = false)
  40.  * @method Image roundedRectangle($x1, $y1, $x2, $y2, $radius, $color, $filled = false)
  41.  * @method Image line($x1, $y1, $x2, $y2, $color = 0x000000)
  42.  * @method Image ellipse($cx, $cy, $width, $height, $color = 0x000000, $filled = false)
  43.  * @method Image circle($cx, $cy, $r, $color = 0x000000, $filled = false)
  44.  * @method Image polygon(array $points, $color, $filled = false)
  45.  * @method Image flip($flipVertical, $flipHorizontal)
  46.  */
  47. class Image
  48. {
  49.     /**
  50.      * Directory to use for file caching.
  51.      */
  52.     protected $cacheDir 'cache/images';
  53.     /**
  54.      * Directory cache mode.
  55.      */
  56.     protected $cacheMode null;
  57.     /**
  58.      * Internal adapter.
  59.      *
  60.      * @var AdapterInterface
  61.      */
  62.     protected $adapter null;
  63.     /**
  64.      * Pretty name for the image.
  65.      */
  66.     protected $prettyName '';
  67.     protected $prettyPrefix;
  68.     /**
  69.      * Transformations hash.
  70.      */
  71.     protected $hash null;
  72.     /**
  73.      * The image source.
  74.      */
  75.     protected $source null;
  76.     /**
  77.      * Force image caching, even if there is no operation applied.
  78.      */
  79.     protected $forceCache true;
  80.     /**
  81.      * Supported types.
  82.      */
  83.     public static $types = array(
  84.         'jpg'   => 'jpeg',
  85.         'jpeg'  => 'jpeg',
  86.         'webp'  => 'webp',
  87.         'png'   => 'png',
  88.         'gif'   => 'gif',
  89.     );
  90.     /**
  91.      * Fallback image.
  92.      */
  93.     protected $fallback;
  94.     /**
  95.      * Use fallback image.
  96.      */
  97.     protected $useFallbackImage true;
  98.     /**
  99.      * Cache system.
  100.      *
  101.      * @var \Gregwar\Cache\CacheInterface
  102.      */
  103.     protected $cache;
  104.     /**
  105.      * Get the cache system.
  106.      *
  107.      * @return \Gregwar\Cache\CacheInterface
  108.      */
  109.     public function getCacheSystem()
  110.     {
  111.         if (is_null($this->cache)) {
  112.             $this->cache = new \Gregwar\Cache\Cache();
  113.             $this->cache->setCacheDirectory($this->cacheDir);
  114.         }
  115.         return $this->cache;
  116.     }
  117.     /**
  118.      * Set the cache system.
  119.      *
  120.      * @param \Gregwar\Cache\CacheInterface $cache
  121.      */
  122.     public function setCacheSystem(CacheInterface $cache)
  123.     {
  124.         $this->cache $cache;
  125.     }
  126.     /**
  127.      * Change the caching directory.
  128.      */
  129.     public function setCacheDir($cacheDir)
  130.     {
  131.         $this->getCacheSystem()->setCacheDirectory($cacheDir);
  132.         return $this;
  133.     }
  134.     /**
  135.      * @param int $dirMode
  136.      */
  137.     public function setCacheDirMode($dirMode)
  138.     {
  139.         $this->cache->setDirectoryMode($dirMode);
  140.     }
  141.     /**
  142.      * Enable or disable to force cache even if the file is unchanged.
  143.      */
  144.     public function setForceCache($forceCache true)
  145.     {
  146.         $this->forceCache $forceCache;
  147.         return $this;
  148.     }
  149.     /**
  150.      * The actual cache dir.
  151.      */
  152.     public function setActualCacheDir($actualCacheDir)
  153.     {
  154.         $this->getCacheSystem()->setActualCacheDirectory($actualCacheDir);
  155.         return $this;
  156.     }
  157.     /**
  158.      * Sets the pretty name of the image.
  159.      */
  160.     public function setPrettyName($name$prefix true)
  161.     {
  162.         if (empty($name)) {
  163.             return $this;
  164.         }
  165.         $this->prettyName $this->urlize($name);
  166.         $this->prettyPrefix $prefix;
  167.         return $this;
  168.     }
  169.     /**
  170.      * Urlizes the prettyName.
  171.      */
  172.     protected function urlize($name)
  173.     {
  174.         $transliterator '\Behat\Transliterator\Transliterator';
  175.         if (class_exists($transliterator)) {
  176.             $name $transliterator::transliterate($name);
  177.             $name $transliterator::urlize($name);
  178.         } else {
  179.             $name strtolower($name);
  180.             $name str_replace(' ''-'$name);
  181.             $name preg_replace('/([^a-z0-9\-]+)/m'''$name);
  182.         }
  183.         return $name;
  184.     }
  185.     /**
  186.      * Operations array.
  187.      */
  188.     protected $operations = array();
  189.     public function __construct($originalFile null$width null$height null)
  190.     {
  191.         $this->setFallback(null);
  192.         if ($originalFile) {
  193.             $this->source = new Source\File($originalFile);
  194.         } else {
  195.             $this->source = new Source\Create($width$height);
  196.         }
  197.     }
  198.     /**
  199.      * Sets the image data.
  200.      */
  201.     public function setData($data)
  202.     {
  203.         $this->source = new Source\Data($data);
  204.     }
  205.     /**
  206.      * Sets the resource.
  207.      */
  208.     public function setResource($resource)
  209.     {
  210.         $this->source = new Source\Resource($resource);
  211.     }
  212.     /**
  213.      * Use the fallback image or not.
  214.      */
  215.     public function useFallback($useFallbackImage true)
  216.     {
  217.         $this->useFallbackImage $useFallbackImage;
  218.         return $this;
  219.     }
  220.     /**
  221.      * Sets the fallback image to use.
  222.      */
  223.     public function setFallback($fallback null)
  224.     {
  225.         if ($fallback === null) {
  226.             $this->fallback __DIR__.'/images/error.jpg';
  227.         } else {
  228.             $this->fallback $fallback;
  229.         }
  230.         return $this;
  231.     }
  232.     /**
  233.      * Gets the fallack image path.
  234.      */
  235.     public function getFallback()
  236.     {
  237.         return $this->fallback;
  238.     }
  239.     /**
  240.      * Gets the fallback into the cache dir.
  241.      */
  242.     public function getCacheFallback()
  243.     {
  244.         $fallback $this->fallback;
  245.         return $this->getCacheSystem()->getOrCreateFile('fallback.jpg', array(), function ($target) use ($fallback) {
  246.             copy($fallback$target);
  247.         });
  248.     }
  249.     /**
  250.      * @return AdapterInterface
  251.      */
  252.     public function getAdapter()
  253.     {
  254.         if (null === $this->adapter) {
  255.             // Defaults to GD
  256.             $this->setAdapter('gd');
  257.         }
  258.         return $this->adapter;
  259.     }
  260.     public function setAdapter($adapter)
  261.     {
  262.         if ($adapter instanceof Adapter\Adapter) {
  263.             $this->adapter $adapter;
  264.         } else {
  265.             if (is_string($adapter)) {
  266.                 $adapter strtolower($adapter);
  267.                 switch ($adapter) {
  268.                 case 'gd':
  269.                     $this->adapter = new Adapter\GD();
  270.                     break;
  271.                 case 'imagemagick':
  272.                 case 'imagick':
  273.                     $this->adapter = new Adapter\Imagick();
  274.                     break;
  275.                 default:
  276.                     throw new \Exception('Unknown adapter: '.$adapter);
  277.                     break;
  278.                 }
  279.             } else {
  280.                 throw new \Exception('Unable to load the given adapter (not string or Adapter)');
  281.             }
  282.         }
  283.         $this->adapter->setSource($this->source);
  284.     }
  285.     /**
  286.      * Get the file path.
  287.      *
  288.      * @return mixed a string with the filen name, null if the image
  289.      *               does not depends on a file
  290.      */
  291.     public function getFilePath()
  292.     {
  293.         if ($this->source instanceof Source\File) {
  294.             return $this->source->getFile();
  295.         } else {
  296.             return;
  297.         }
  298.     }
  299.     /**
  300.      * Defines the file only after instantiation.
  301.      *
  302.      * @param string $originalFile the file path
  303.      */
  304.     public function fromFile($originalFile)
  305.     {
  306.         $this->source = new Source\File($originalFile);
  307.         return $this;
  308.     }
  309.     /**
  310.      * Tells if the image is correct.
  311.      */
  312.     public function correct()
  313.     {
  314.         return $this->source->correct();
  315.     }
  316.     /**
  317.      * Guess the file type.
  318.      */
  319.     public function guessType()
  320.     {
  321.         return $this->source->guessType();
  322.     }
  323.     /**
  324.      * Adds an operation.
  325.      */
  326.     protected function addOperation($method$args)
  327.     {
  328.         $this->operations[] = array($method$args);
  329.     }
  330.     /**
  331.      * Generic function.
  332.      */
  333.     public function __call($methodName$args)
  334.     {
  335.         $adapter $this->getAdapter();
  336.         $reflection = new \ReflectionClass(get_class($adapter));
  337.         if ($reflection->hasMethod($methodName)) {
  338.             $method $reflection->getMethod($methodName);
  339.             if ($method->getNumberOfRequiredParameters() > count($args)) {
  340.                 throw new \InvalidArgumentException('Not enough arguments given for '.$methodName);
  341.             }
  342.             $this->addOperation($methodName$args);
  343.             return $this;
  344.         }
  345.         throw new \BadFunctionCallException('Invalid method: '.$methodName);
  346.     }
  347.     /**
  348.      * Serialization of operations.
  349.      */
  350.     public function serializeOperations()
  351.     {
  352.         $datas = array();
  353.         foreach ($this->operations as $operation) {
  354.             $method $operation[0];
  355.             $args $operation[1];
  356.             foreach ($args as &$arg) {
  357.                 if ($arg instanceof self) {
  358.                     $arg $arg->getHash();
  359.                 }
  360.             }
  361.             $datas[] = array($method$args);
  362.         }
  363.         return serialize($datas);
  364.     }
  365.     /**
  366.      * Generates the hash.
  367.      */
  368.     public function generateHash($type 'guess'$quality 80)
  369.     {
  370.         $inputInfos $this->source->getInfos();
  371.         $datas = array(
  372.             $inputInfos,
  373.             $this->serializeOperations(),
  374.             $type,
  375.             $quality,
  376.         );
  377.         $this->hash sha1(serialize($datas));
  378.     }
  379.     /**
  380.      * Gets the hash.
  381.      */
  382.     public function getHash($type 'guess'$quality 80)
  383.     {
  384.         if (null === $this->hash) {
  385.             $this->generateHash($type$quality);
  386.         }
  387.         return $this->hash;
  388.     }
  389.     /**
  390.      * Gets the cache file name and generate it if it does not exists.
  391.      * Note that if it exists, all the image computation process will
  392.      * not be done.
  393.      *
  394.      * @param string $type    the image type
  395.      * @param int    $quality the quality (for JPEG)
  396.      */
  397.     public function cacheFile($type 'jpg'$quality 80$actual false)
  398.     {
  399.         if ($type == 'guess') {
  400.             $type $this->guessType();
  401.         }
  402.         if (!count($this->operations) && $type == $this->guessType() && !$this->forceCache) {
  403.             return $this->getFilename($this->getFilePath());
  404.         }
  405.         // Computes the hash
  406.         $this->hash $this->getHash($type$quality);
  407.         // Generates the cache file
  408.         $cacheFile '';
  409.         if (!$this->prettyName || $this->prettyPrefix) {
  410.             $cacheFile .= $this->hash;
  411.         }
  412.         if ($this->prettyPrefix) {
  413.             $cacheFile .= '-';
  414.         }
  415.         if ($this->prettyName) {
  416.             $cacheFile .= $this->prettyName;
  417.         }
  418.         $cacheFile .= '.'.$type;
  419.         // If the files does not exists, save it
  420.         $image $this;
  421.         // Target file should be younger than all the current image
  422.         // dependencies
  423.         $conditions = array(
  424.             'younger-than' => $this->getDependencies(),
  425.         );
  426.         // The generating function
  427.         $generate = function ($target) use ($image$type$quality) {
  428.             $result $image->save($target$type$quality);
  429.             if ($result != $target) {
  430.                 throw new GenerationError($result);
  431.             }
  432.         };
  433.         // Asking the cache for the cacheFile
  434.         try {
  435.             $file $this->getCacheSystem()->getOrCreateFile($cacheFile$conditions$generate$actual);
  436.         } catch (GenerationError $e) {
  437.             $file $e->getNewFile();
  438.         }
  439.         // Nulling the resource
  440.         $this->getAdapter()->setSource(new Source\File($file));
  441.         $this->getAdapter()->deinit();
  442.         if ($actual) {
  443.             return $file;
  444.         } else {
  445.             return $this->getFilename($file);
  446.         }
  447.     }
  448.     /**
  449.      * Get cache data (to render the image).
  450.      *
  451.      * @param string $type    the image type
  452.      * @param int    $quality the quality (for JPEG)
  453.      */
  454.     public function cacheData($type 'jpg'$quality 80)
  455.     {
  456.         return file_get_contents($this->cacheFile($type$quality));
  457.     }
  458.     /**
  459.      * Hook to helps to extends and enhance this class.
  460.      */
  461.     protected function getFilename($filename)
  462.     {
  463.         return $filename;
  464.     }
  465.     /**
  466.      * Generates and output a jpeg cached file.
  467.      */
  468.     public function jpeg($quality 80)
  469.     {
  470.         return $this->cacheFile('jpg'$quality);
  471.     }
  472.     /**
  473.      * Generates and output a gif cached file.
  474.      */
  475.     public function gif()
  476.     {
  477.         return $this->cacheFile('gif');
  478.     }
  479.     /**
  480.      * Generates and output a png cached file.
  481.      */
  482.     public function png()
  483.     {
  484.         return $this->cacheFile('png');
  485.     }
  486.     /**
  487.      * Generates and output a webp cached file.
  488.      */
  489.     public function webp($quality 80)
  490.     {
  491.         return $this->cacheFile('webp'$quality);
  492.     }
  493.     /**
  494.      * Generates and output an image using the same type as input.
  495.      */
  496.     public function guess($quality 80)
  497.     {
  498.         return $this->cacheFile('guess'$quality);
  499.     }
  500.     /**
  501.      * Get all the files that this image depends on.
  502.      *
  503.      * @return string[] this is an array of strings containing all the files that the
  504.      *                  current Image depends on
  505.      */
  506.     public function getDependencies()
  507.     {
  508.         $dependencies = array();
  509.         $file $this->getFilePath();
  510.         if ($file) {
  511.             $dependencies[] = $file;
  512.         }
  513.         foreach ($this->operations as $operation) {
  514.             foreach ($operation[1] as $argument) {
  515.                 if ($argument instanceof self) {
  516.                     $dependencies array_merge($dependencies$argument->getDependencies());
  517.                 }
  518.             }
  519.         }
  520.         return $dependencies;
  521.     }
  522.     /**
  523.      * Applies the operations.
  524.      */
  525.     public function applyOperations()
  526.     {
  527.         // Renders the effects
  528.         foreach ($this->operations as $operation) {
  529.             call_user_func_array(array($this->adapter$operation[0]), $operation[1]);
  530.         }
  531.     }
  532.     /**
  533.      * Initialize the adapter.
  534.      */
  535.     public function init()
  536.     {
  537.         $this->getAdapter()->init();
  538.     }
  539.     /**
  540.      * Save the file to a given output.
  541.      */
  542.     public function save($file$type 'guess'$quality 80)
  543.     {
  544.         if ($file) {
  545.             $directory dirname($file);
  546.             if (!is_dir($directory)) {
  547.                 @mkdir($directory0777true);
  548.             }
  549.         }
  550.         if (is_int($type)) {
  551.             $quality $type;
  552.             $type 'jpeg';
  553.         }
  554.         if ($type == 'guess') {
  555.             $type $this->guessType();
  556.         }
  557.         if (!isset(self::$types[$type])) {
  558.             throw new \InvalidArgumentException('Given type ('.$type.') is not valid');
  559.         }
  560.         $type self::$types[$type];
  561.         try {
  562.             $this->init();
  563.             $this->applyOperations();
  564.             $success false;
  565.             if (null == $file) {
  566.                 ob_start();
  567.             }
  568.             if ($type == 'jpeg') {
  569.                 $success $this->getAdapter()->saveJpeg($file$quality);
  570.             }
  571.             if ($type == 'gif') {
  572.                 $success $this->getAdapter()->saveGif($file);
  573.             }
  574.             if ($type == 'png') {
  575.                 $success $this->getAdapter()->savePng($file);
  576.             }
  577.             if ($type == 'webp') {
  578.                 $success $this->getAdapter()->saveWebP($file$quality);
  579.             }
  580.             if (!$success) {
  581.                 return false;
  582.             }
  583.             return null === $file ob_get_clean() : $file;
  584.         } catch (\Exception $e) {
  585.             if ($this->useFallbackImage) {
  586.                 return null === $file file_get_contents($this->fallback) : $this->getCacheFallback();
  587.             } else {
  588.                 throw $e;
  589.             }
  590.         }
  591.     }
  592.     /**
  593.      * Get the contents of the image.
  594.      */
  595.     public function get($type 'guess'$quality 80)
  596.     {
  597.         return $this->save(null$type$quality);
  598.     }
  599.     /* Image API */
  600.     /**
  601.      * Image width.
  602.      */
  603.     public function width()
  604.     {
  605.         return $this->getAdapter()->width();
  606.     }
  607.     /**
  608.      * Image height.
  609.      */
  610.     public function height()
  611.     {
  612.         return $this->getAdapter()->height();
  613.     }
  614.     /**
  615.      * Tostring defaults to jpeg.
  616.      */
  617.     public function __toString()
  618.     {
  619.         return $this->guess();
  620.     }
  621.     /**
  622.      * Returning basic html code for this image.
  623.      */
  624.     public function html($title ''$type 'jpg'$quality 80)
  625.     {
  626.         return '<img title="'.$title.'" src="'.$this->cacheFile($type$quality).'" />';
  627.     }
  628.     /**
  629.      * Returns the Base64 inlinable representation.
  630.      */
  631.     public function inline($type 'jpg'$quality 80)
  632.     {
  633.         $mime $type;
  634.         if ($mime == 'jpg') {
  635.             $mime 'jpeg';
  636.         }
  637.         return 'data:image/'.$mime.';base64,'.base64_encode(file_get_contents($this->cacheFile($type$qualitytrue)));
  638.     }
  639.     /**
  640.      * Creates an instance, usefull for one-line chaining.
  641.      */
  642.     public static function open($file '')
  643.     {
  644.         return new static($file);
  645.     }
  646.     /**
  647.      * Creates an instance of a new resource.
  648.      */
  649.     public static function create($width$height)
  650.     {
  651.         return new static(null$width$height);
  652.     }
  653.     /**
  654.      * Creates an instance of image from its data.
  655.      */
  656.     public static function fromData($data)
  657.     {
  658.         $image = new static();
  659.         $image->setData($data);
  660.         return $image;
  661.     }
  662.     /**
  663.      * Creates an instance of image from resource.
  664.      */
  665.     public static function fromResource($resource)
  666.     {
  667.         $image = new static();
  668.         $image->setResource($resource);
  669.         return $image;
  670.     }
  671. }