Watimage API
  • Namespace
  • Class

Namespaces

  • Elboletaire
    • Watimage
      • Exception

Classes

  • Elboletaire\Watimage\Image
  • Elboletaire\Watimage\Normalize
  • Elboletaire\Watimage\Watermark
  • Elboletaire\Watimage\Watimage

Exceptions

  • Elboletaire\Watimage\Exception\ExtensionNotLoadedException
  • Elboletaire\Watimage\Exception\FileNotExistException
  • Elboletaire\Watimage\Exception\InvalidArgumentException
  • Elboletaire\Watimage\Exception\InvalidExtensionException
  • Elboletaire\Watimage\Exception\InvalidMimeException
   1 <?php
   2 namespace Elboletaire\Watimage;
   3 
   4 use Exception;
   5 use Elboletaire\Watimage\Exception\ExtensionNotLoadedException;
   6 use Elboletaire\Watimage\Exception\FileNotExistException;
   7 use Elboletaire\Watimage\Exception\InvalidArgumentException;
   8 use Elboletaire\Watimage\Exception\InvalidExtensionException;
   9 use Elboletaire\Watimage\Exception\InvalidMimeException;
  10 
  11 /**
  12  * The Image class. The main code of Watimage.
  13  *
  14  * @author Òscar Casajuana <elboletaire at underave dot net>
  15  * @copyright 2015 Òscar Casajuana <elboletaire at underave dot net>
  16  * @license https://opensource.org/licenses/MIT MIT
  17  * @link https://github.com/elboletaire/Watimage
  18  */
  19 class Image
  20 {
  21     /**
  22      * Constant for the (deprecated) transparent color
  23      */
  24     const COLOR_TRANSPARENT = -1;
  25 
  26     /**
  27      * Current image location.
  28      *
  29      * @var string
  30      */
  31     protected $filename;
  32 
  33     /**
  34      * Image GD resource.
  35      *
  36      * @var resource
  37      */
  38     protected $image;
  39 
  40     /**
  41      * Image metadata.
  42      *
  43      * @var array
  44      */
  45     protected $metadata = [];
  46 
  47     /**
  48      * Current image width
  49      *
  50      * @var float
  51      */
  52     protected $width;
  53 
  54     /**
  55      * Current image height
  56      *
  57      * @var float
  58      */
  59     protected $height;
  60 
  61     /**
  62      * Image export quality for gif and jpg files.
  63      *
  64      * You can set it with setQuality or setImage methods.
  65      *
  66      * @var integer
  67      */
  68     protected $quality = 80;
  69 
  70     /**
  71      * Image compression value for png files.
  72      *
  73      * You can set it with setCompression method.
  74      *
  75      * @var integer
  76      */
  77     protected $compression = 9;
  78 
  79     /**
  80      * Constructor method. You can pass a filename to be loaded by default
  81      * or load it later with load('filename.ext')
  82      *
  83      * @param string $file Filepath of the image to be loaded.
  84      */
  85     public function __construct($file = null, $autoOrientate = true)
  86     {
  87         if (!extension_loaded('gd')) {
  88             throw new ExtensionNotLoadedException("GD");
  89         }
  90 
  91         if (!empty($file)) {
  92             $this->load($file);
  93 
  94             if ($autoOrientate) {
  95                 $this->autoOrientate();
  96             }
  97         }
  98     }
  99 
 100     /**
 101      * Ensure everything gets emptied on object destruction.
 102      *
 103      * @codeCoverageIgnore
 104      */
 105     public function __destruct()
 106     {
 107         $this->destroy();
 108     }
 109 
 110     /**
 111      * Creates a resource image.
 112      *
 113      * This method was using imagecreatefromstring but I decided to switch after
 114      * reading this: https://thenewphalls.wordpress.com/2012/12/27/imagecreatefromstring-vs-imagecreatefromformat
 115      *
 116      * @param  string $filename Image file path/name.
 117      * @param  string $mime     Image mime or `string` if creating from string
 118      *                          (no base64 encoded).
 119      * @return resource
 120      * @throws InvalidMimeException
 121      */
 122     public function createResourceImage($filename, $mime)
 123     {
 124         switch ($mime) {
 125             case 'image/gif':
 126                 $image = imagecreatefromgif($filename);
 127                 break;
 128 
 129             case 'image/png':
 130                 $image = imagecreatefrompng($filename);
 131                 break;
 132 
 133             case 'image/jpeg':
 134                 $image =  imagecreatefromjpeg($filename);
 135                 break;
 136 
 137             case 'string':
 138                 $image = imagecreatefromstring($filename);
 139                 break;
 140 
 141             default:
 142                 throw new InvalidMimeException($mime);
 143         }
 144 
 145         // Handle transparencies
 146         imagesavealpha($image, true);
 147         imagealphablending($image, true);
 148 
 149         return $image;
 150     }
 151 
 152     /**
 153      * Cleans up everything to start again.
 154      *
 155      * @return Image
 156      */
 157     public function destroy()
 158     {
 159         if (!empty($this->image)
 160             && is_resource($this->image)
 161             && get_resource_type($this->image) == 'gd'
 162         ) {
 163             imagedestroy($this->image);
 164         }
 165         $this->metadata = [];
 166         $this->filename = $this->width = $this->height = null;
 167 
 168         return $this;
 169     }
 170 
 171     /**
 172      * Outputs or saves the image.
 173      *
 174      * @param  string $filename Filename to be saved. Empty to directly print on screen.
 175      * @param  string $output   Use it to overwrite the output format when no $filename is passed.
 176      * @param  bool   $header   Wheather or not generate the output header.
 177      * @return Image
 178      * @throws InvalidArgumentException If output format is not recognised.
 179      */
 180     public function generate($filename = null, $output = null, $header = true)
 181     {
 182         $output = $output ?: $this->metadata['mime'];
 183         if (!empty($filename)) {
 184             $output = $this->getMimeFromExtension($filename);
 185         } elseif ($header) {
 186             header("Content-type: {$output}");
 187         }
 188 
 189         switch ($output) {
 190             case 'image/gif':
 191                 imagegif($this->image, $filename, $this->quality);
 192                 break;
 193             case 'image/png':
 194                 imagesavealpha($this->image, true);
 195                 imagepng($this->image, $filename, $this->compression);
 196                 break;
 197             case 'image/jpeg':
 198                 imageinterlace($this->image, true);
 199                 imagejpeg($this->image, $filename, $this->quality);
 200                 break;
 201             default:
 202                 throw new InvalidArgumentException("Invalid output format \"%s\"", $output);
 203         }
 204 
 205         return $this;
 206     }
 207 
 208     /**
 209      * Similar to generate, except that passing an empty $filename here will
 210      * overwrite the original file.
 211      *
 212      * @param  string $filename Filename to be saved. Empty to overwrite original file.
 213      * @return bool
 214      */
 215     public function save($filename = null)
 216     {
 217         $filename = $filename ?: $this->filename;
 218 
 219         return $this->generate($filename);
 220     }
 221 
 222     /**
 223      * Returns the base64 version for the current Image.
 224      *
 225      * @param  bool $prefix Whether or not prefix the string
 226      *                      with `data:{mime};base64,`.
 227      * @return string
 228      */
 229     public function toString($prefix = false)
 230     {
 231         ob_start();
 232         $this->generate(null, null, false);
 233         $image = ob_get_contents();
 234         ob_end_clean();
 235 
 236         $string = base64_encode($image);
 237 
 238         if ($prefix) {
 239             $prefix = "data:{$this->metadata['mime']};base64,";
 240             $string = $prefix . $string;
 241         }
 242 
 243         return $string;
 244     }
 245 
 246     /**
 247      *  Loads image and (optionally) its options.
 248      *
 249      *  @param mixed $filename Filename string or array containing both filename and quality
 250      *  @return Watimage
 251      *  @throws FileNotExistException
 252      *  @throws InvalidArgumentException
 253      */
 254     public function load($filename)
 255     {
 256         if (empty($filename)) {
 257             throw new InvalidArgumentException("Image file has not been set.");
 258         }
 259 
 260         if (is_array($filename)) {
 261             if (isset($filename['quality'])) {
 262                 $this->setQuality($filename['quality']);
 263             }
 264             $filename = $filename['file'];
 265         }
 266 
 267         if (!file_exists($filename)) {
 268             throw new FileNotExistException($filename);
 269         }
 270 
 271         $this->destroy();
 272 
 273         $this->filename = $filename;
 274         $this->getMetadataForImage();
 275         $this->image = $this->createResourceImage($filename, $this->metadata['mime']);
 276 
 277         return $this;
 278     }
 279 
 280     /**
 281      * Loads an image from string. Can be either base64 encoded or not.
 282      *
 283      * @param  string $string The image string to be loaded.
 284      * @return Image
 285      */
 286     public function fromString($string)
 287     {
 288         if (strpos($string, 'data:image') === 0) {
 289             preg_match('/^data:(image\/[a-z]+);base64,(.+)/', $string, $matches);
 290             array_shift($matches);
 291             list($this->metadata['mime'], $string) = $matches;
 292         }
 293 
 294         if (!$string = base64_decode($string)) {
 295             throw new InvalidArgumentException(
 296                 'The given value does not seem a valid base64 string'
 297             );
 298         }
 299 
 300         $this->image = $this->createResourceImage($string, 'string');
 301         $this->updateSize();
 302 
 303         if (function_exists('finfo_buffer') && !isset($this->metadata['mime'])) {
 304             $finfo = finfo_open();
 305             $this->metadata['mime'] = finfo_buffer($finfo, $string, FILEINFO_MIME_TYPE);
 306             finfo_close($finfo);
 307         }
 308 
 309         return $this;
 310     }
 311 
 312     /**
 313      * Auto-orients an image based on its exif Orientation information.
 314      *
 315      * @return Image
 316      */
 317     public function autoOrientate()
 318     {
 319         if (empty($this->metadata['exif']['Orientation'])) {
 320             return $this;
 321         }
 322 
 323         switch ((int)$this->metadata['exif']['Orientation']) {
 324             case 2:
 325                 return $this->flip('horizontal');
 326             case 3:
 327                 return $this->flip('both');
 328             case 4:
 329                 return $this->flip('vertical');
 330             case 5:
 331                 $this->flip('horizontal');
 332                 return $this->rotate(-90);
 333             case 6:
 334                 return $this->rotate(-90);
 335             case 7:
 336                 $this->flip('horizontal');
 337                 return $this->rotate(90);
 338             case 8:
 339                 return $this->rotate(90);
 340             default:
 341                 return $this;
 342         }
 343     }
 344 
 345     /**
 346      * Rotates an image.
 347      *
 348      * Will rotate clockwise when using positive degrees.
 349      *
 350      * @param  int    $degrees Rotation angle in degrees.
 351      * @param  mixed  $bgcolor Background to be used for the background, transparent by default.
 352      * @return Image
 353      */
 354     public function rotate($degrees, $bgcolor = self::COLOR_TRANSPARENT)
 355     {
 356         $bgcolor = $this->color($bgcolor);
 357 
 358         $this->image = imagerotate($this->image, $degrees, $bgcolor);
 359 
 360         $this->updateSize();
 361 
 362         return $this;
 363     }
 364 
 365     /**
 366      * All in one method for all resize methods.
 367      *
 368      * @param  string $type   Type of resize: resize, resizemin, reduce, crop & resizecrop.
 369      * @param  mixed  $width  Can be just max width or an array containing both params.
 370      * @param  int    $height Max height.
 371      * @return Image
 372      */
 373     public function resize($type, $width, $height = null)
 374     {
 375         $types = [
 376             'classic'    => 'classicResize',
 377             'resize'     => 'classicResize',
 378             'reduce'     => 'reduce',
 379             'resizemin'  => 'reduce',
 380             'min'        => 'reduce',
 381             'crop'       => 'classicCrop',
 382             'resizecrop' => 'resizeCrop'
 383         ];
 384 
 385         $lowertype = strtolower($type);
 386 
 387         if (!array_key_exists($lowertype, $types)) {
 388             throw new InvalidArgumentException("Invalid resize type %s.", $type);
 389         }
 390 
 391         return $this->{$types[$lowertype]}($width, $height);
 392     }
 393 
 394     /**
 395      * Resizes maintaining aspect ratio.
 396      *
 397      * Maintains the aspect ratio of the image and makes sure that it fits
 398      * within the max width and max height (thus some side will be smaller).
 399      *
 400      * @param  mixed $width  Can be just max width or an array containing both params.
 401      * @param  int   $height Max height.
 402      * @return Image
 403      */
 404     public function classicResize($width, $height = null)
 405     {
 406         list($width, $height) = Normalize::size($width, $height);
 407 
 408         if ($this->width == $width && $this->height == $height) {
 409             return $this;
 410         }
 411 
 412         if ($this->width > $this->height) {
 413             $height = ($this->height * $width) / $this->width;
 414         } elseif ($this->width < $this->height) {
 415             $width = ($this->width * $height) / $this->height;
 416         } elseif ($this->width == $this->height) {
 417             $width = $height;
 418         }
 419 
 420         $this->image = $this->imagecopy($width, $height);
 421 
 422         $this->updateSize();
 423 
 424         return $this;
 425     }
 426 
 427     /**
 428      * Backwards compatibility alias for reduce (which has the same logic).
 429      *
 430      * @param  mixed $width  Can be just max width or an array containing both params.
 431      * @param  int   $height Max height.
 432      * @return Image
 433      * @deprecated
 434      * @codeCoverageIgnore
 435      */
 436     public function resizeMin($width, $height = null)
 437     {
 438         return $this->reduce($width, $height);
 439     }
 440 
 441     /**
 442      * A straight centered crop.
 443      *
 444      * @param  mixed $width  Can be just max width or an array containing both params.
 445      * @param  int   $height Max height.
 446      * @return Image
 447      */
 448     public function classicCrop($width, $height = null)
 449     {
 450         list($width, $height) = Normalize::size($width, $height);
 451 
 452         $startY = ($this->height - $height) / 2;
 453         $startX = ($this->width - $width) / 2;
 454 
 455         $this->image = $this->imagecopy($width, $height, $startX, $startY, $width, $height);
 456 
 457         $this->updateSize();
 458 
 459         return $this;
 460     }
 461 
 462     /**
 463      * Resizes to max, then crops to center.
 464      *
 465      * @param  mixed $width  Can be just max width or an array containing both params.
 466      * @param  int   $height Max height.
 467      * @return Image
 468      */
 469     public function resizeCrop($width, $height = null)
 470     {
 471         list($width, $height) = Normalize::size($width, $height);
 472 
 473         $ratioX = $width / $this->width;
 474         $ratioY = $height / $this->height;
 475         $srcW = $this->width;
 476         $srcH = $this->height;
 477 
 478         if ($ratioX < $ratioY) {
 479             $startX = round(($this->width - ($width / $ratioY)) / 2);
 480             $startY = 0;
 481             $srcW = round($width / $ratioY);
 482         } else {
 483             $startX = 0;
 484             $startY = round(($this->height - ($height / $ratioX)) / 2);
 485             $srcH = round($height / $ratioX);
 486         }
 487 
 488         $this->image = $this->imagecopy($width, $height, $startX, $startY, $srcW, $srcH);
 489 
 490         $this->updateSize();
 491 
 492         return $this;
 493     }
 494 
 495     /**
 496      * Resizes maintaining aspect ratio but not exceeding width / height.
 497      *
 498      * @param  mixed $width  Can be just max width or an array containing both params.
 499      * @param  int   $height Max height.
 500      * @return Image
 501      */
 502     public function reduce($width, $height = null)
 503     {
 504         list($width, $height) = Normalize::size($width, $height);
 505 
 506         if ($this->width < $width && $this->height < $height) {
 507             return $this;
 508         }
 509 
 510         $ratioX = $this->width / $width;
 511         $ratioY = $this->height / $height;
 512 
 513         $ratio = $ratioX > $ratioY ? $ratioX : $ratioY;
 514 
 515         if ($ratio === 1) {
 516             return $this;
 517         }
 518 
 519         // Getting the new image size
 520         $width = (int)($this->width / $ratio);
 521         $height = (int)($this->height / $ratio);
 522 
 523         $this->image = $this->imagecopy($width, $height);
 524 
 525         $this->updateSize();
 526 
 527         return $this;
 528     }
 529 
 530     /**
 531      * Flips an image. If PHP version is 5.5.0 or greater will use
 532      * proper php gd imageflip method. Otherwise will fallback to
 533      * convenienceflip.
 534      *
 535      * @param  string $type Type of flip, can be any of: horizontal, vertical, both
 536      * @return Image
 537      */
 538     public function flip($type = 'horizontal')
 539     {
 540         if (version_compare(PHP_VERSION, '5.5.0', '<')) {
 541             return $this->convenienceFlip($type);
 542         }
 543 
 544         imageflip($this->image, Normalize::flip($type));
 545 
 546         return $this;
 547     }
 548 
 549     /**
 550      * Flip method for PHP versions < 5.5.0
 551      *
 552      * @param  string $type Type of flip, can be any of: horizontal, vertical, both
 553      * @return Image
 554      */
 555     public function convenienceFlip($type = 'horizontal')
 556     {
 557         $type = Normalize::flip($type);
 558 
 559         $resampled = $this->imagecreate($this->width, $this->height);
 560 
 561         // @codingStandardsIgnoreStart
 562         switch ($type) {
 563             case IMG_FLIP_VERTICAL:
 564                 imagecopyresampled(
 565                     $resampled, $this->image,
 566                     0, 0, 0, ($this->height - 1),
 567                     $this->width, $this->height, $this->width, 0 - $this->height
 568                 );
 569                 break;
 570             case IMG_FLIP_HORIZONTAL:
 571                 imagecopyresampled(
 572                     $resampled, $this->image,
 573                     0, 0, ($this->width - 1), 0,
 574                     $this->width, $this->height, 0 - $this->width, $this->height
 575                 );
 576                 break;
 577             // same as $this->rotate(180)
 578             case IMG_FLIP_BOTH:
 579                 imagecopyresampled(
 580                     $resampled, $this->image,
 581                     0, 0, ($this->width - 1), ($this->height - 1),
 582                     $this->width, $this->height, 0 - $this->width, 0 - $this->height
 583                 );
 584                 break;
 585         }
 586         // @codingStandardsIgnoreEnd
 587 
 588         $this->image = $resampled;
 589 
 590         return $this;
 591     }
 592 
 593     /**
 594      * Creates an empty canvas.
 595      *
 596      * If no arguments are passed and we have previously created an
 597      * image it will create a new canvas with the previous canvas size.
 598      * Due to this, you can use this method to "empty" the current canvas.
 599      *
 600      * @param  int $width  Canvas width.
 601      * @param  int $height Canvas height.
 602      * @return Image
 603      */
 604     public function create($width = null, $height = null)
 605     {
 606         if (!isset($width)) {
 607             if (!isset($this->width, $this->height)) {
 608                 throw new InvalidArgumentException("You must set the canvas size.");
 609             }
 610             $width = $this->width;
 611             $height = $this->height;
 612         }
 613 
 614         if (!isset($height)) {
 615             $height = $width;
 616         }
 617 
 618         $this->image = $this->imagecreate($width, $height);
 619         $exif = null;
 620         $this->metadata = compact('width', 'height', 'exif');
 621 
 622         $this->updateSize();
 623 
 624         return $this;
 625     }
 626 
 627     /**
 628      * Creates an empty canvas.
 629      *
 630      * @param  int  $width         Canvas width.
 631      * @param  int  $height        Canvas height.
 632      * @param  bool $transparency  Whether or not to set transparency values.
 633      * @return resource            Image resource with the canvas.
 634      */
 635     protected function imagecreate($width, $height, $transparency = true)
 636     {
 637         $image = imagecreatetruecolor($width, $height);
 638 
 639         if ($transparency) {
 640             // Required for transparencies
 641             $bgcolor = imagecolortransparent(
 642                 $image,
 643                 imagecolorallocatealpha($image, 255, 255, 255, 127)
 644             );
 645             imagefill($image, 0, 0, $bgcolor);
 646             imagesavealpha($image, true);
 647             imagealphablending($image, true);
 648         }
 649 
 650         return $image;
 651     }
 652 
 653     /**
 654      * Helper method for all resize methods and others that require
 655      * imagecopyresampled method.
 656      *
 657      * @param  int  $dstW New width.
 658      * @param  int  $dstH New height.
 659      * @param  int  $srcX Starting source point X.
 660      * @param  int  $srcY Starting source point Y.
 661      * @return resource    GD image resource containing the resized image.
 662      */
 663     protected function imagecopy($dstW, $dstH, $srcX = 0, $srcY = 0, $srcW = false, $srcH = false)
 664     {
 665         $destImage = $this->imagecreate($dstW, $dstH);
 666 
 667         if ($srcW === false) {
 668             $srcW = $this->width;
 669         }
 670 
 671         if ($srcH === false) {
 672             $srcH = $this->height;
 673         }
 674 
 675         // @codingStandardsIgnoreStart
 676         imagecopyresampled(
 677             $destImage, $this->image,
 678             0, 0, $srcX, $srcY,
 679             $dstW, $dstH, $srcW, $srcH
 680         );
 681         // @codingStandardsIgnoreEnd
 682 
 683         return $destImage;
 684     }
 685 
 686     /**
 687      * Fills current canvas with specified color.
 688      *
 689      * It works with newly created canvas. If you want to overwrite the current
 690      * canvas you must first call `create` method to empty current canvas.
 691      *
 692      * @param  mixed $color The color. Check out getColorArray for allowed formats.
 693      * @return Image
 694      */
 695     public function fill($color = '#fff')
 696     {
 697         imagefill($this->image, 0, 0, $this->color($color));
 698 
 699         return $this;
 700     }
 701 
 702     /**
 703      * Allocates a color for the current image resource and returns it.
 704      *
 705      * Useful for directly treating images.
 706      *
 707      * @param  mixed $color The color. Check out getColorArray for allowed formats.
 708      * @return int
 709      * @codeCoverageIgnore
 710      */
 711     public function color($color)
 712     {
 713         $color = Normalize::color($color);
 714 
 715         if ($color['a'] !== 0) {
 716             return imagecolorallocatealpha($this->image, $color['r'], $color['g'], $color['b'], $color['a']);
 717         }
 718 
 719         return imagecolorallocate($this->image, $color['r'], $color['g'], $color['b']);
 720     }
 721 
 722     /**
 723      * Crops an image based on specified coords and size.
 724      *
 725      * You can pass arguments one by one or an array passing arguments
 726      * however you like.
 727      *
 728      * @param  int $x      X position where start to crop.
 729      * @param  int $y      Y position where start to crop.
 730      * @param  int $width  New width of the image.
 731      * @param  int $height New height of the image.
 732      * @return Image
 733      */
 734     public function crop($x, $y = null, $width = null, $height = null)
 735     {
 736         list($x, $y, $width, $height) = Normalize::crop($x, $y, $width, $height);
 737 
 738         $crop = $this->imagecreate($width, $height);
 739 
 740         // @codingStandardsIgnoreStart
 741         imagecopyresampled(
 742             $crop, $this->image,
 743             0, 0, $x, $y,
 744             $width, $height, $width, $height
 745         );
 746         // @codingStandardsIgnoreEnd
 747 
 748         $this->image = $crop;
 749 
 750         $this->updateSize();
 751 
 752         return $this;
 753     }
 754 
 755     /**
 756      * Blurs the image.
 757      *
 758      * @param  mixed   $type   Type of blur to be used between: gaussian, selective.
 759      * @param  integer $passes Number of times to apply the filter.
 760      * @return Image
 761      * @throws InvalidArgumentException
 762      */
 763     public function blur($type = null, $passes = 1)
 764     {
 765         switch (strtolower($type)) {
 766             case IMG_FILTER_GAUSSIAN_BLUR:
 767             case 'selective':
 768                 $type = IMG_FILTER_GAUSSIAN_BLUR;
 769                 break;
 770 
 771             // gaussian by default (just because I like it more)
 772             case null:
 773             case 'gaussian':
 774             case IMG_FILTER_SELECTIVE_BLUR:
 775                 $type = IMG_FILTER_SELECTIVE_BLUR;
 776                 break;
 777 
 778             default:
 779                 throw new InvalidArgumentException("Incorrect blur type \"%s\"", $type);
 780         }
 781 
 782         for ($i = 0; $i < Normalize::fitInRange($passes, 1); $i++) {
 783             imagefilter($this->image, $type);
 784         }
 785 
 786         return $this;
 787     }
 788 
 789     /**
 790      * Changes the brightness of the image.
 791      *
 792      * @param  integer $level Brightness value; range between -255 & 255.
 793      * @return Image
 794      */
 795     public function brightness($level)
 796     {
 797         imagefilter(
 798             $this->image,
 799             IMG_FILTER_BRIGHTNESS,
 800             Normalize::fitInRange($level, -255, 255)
 801         );
 802 
 803         return $this;
 804     }
 805 
 806     /**
 807      * Like grayscale, except you can specify the color.
 808      *
 809      * @param  mixed  $color Color in any format accepted by Normalize::color
 810      * @return Image
 811      */
 812     public function colorize($color)
 813     {
 814         $color = Normalize::color($color);
 815 
 816         imagefilter(
 817             $this->image,
 818             IMG_FILTER_COLORIZE,
 819             $color['r'],
 820             $color['g'],
 821             $color['b'],
 822             $color['a']
 823         );
 824 
 825         return $this;
 826     }
 827 
 828     /**
 829      * Changes the contrast of the image.
 830      *
 831      * @param  integer $level Use for adjunting level of contrast (-100 to 100)
 832      * @return Image
 833      */
 834     public function contrast($level)
 835     {
 836         imagefilter(
 837             $this->image,
 838             IMG_FILTER_CONTRAST,
 839             Normalize::fitInRange($level, -100, 100)
 840         );
 841 
 842         return $this;
 843     }
 844 
 845     /**
 846      * Uses edge detection to highlight the edges in the image.
 847      *
 848      * @return Image
 849      */
 850     public function edgeDetection()
 851     {
 852         imagefilter($this->image, IMG_FILTER_EDGEDETECT);
 853 
 854         return $this;
 855     }
 856 
 857     /**
 858      * Embosses the image.
 859      *
 860      * @return Image
 861      */
 862     public function emboss()
 863     {
 864         imagefilter($this->image, IMG_FILTER_EMBOSS);
 865 
 866         return $this;
 867     }
 868 
 869     /**
 870      * Applies grayscale filter.
 871      *
 872      * @return Image
 873      */
 874     public function grayscale()
 875     {
 876         imagefilter($this->image, IMG_FILTER_GRAYSCALE);
 877 
 878         return $this;
 879     }
 880 
 881     /**
 882      * Uses mean removal to achieve a "sketchy" effect.
 883      *
 884      * @return Image
 885      */
 886     public function meanRemove()
 887     {
 888         imagefilter($this->image, IMG_FILTER_MEAN_REMOVAL);
 889 
 890         return $this;
 891     }
 892 
 893     /**
 894      * Reverses all colors of the image.
 895      *
 896      * @return Image
 897      */
 898     public function negate()
 899     {
 900         imagefilter($this->image, IMG_FILTER_NEGATE);
 901 
 902         return $this;
 903     }
 904 
 905     /**
 906      * Pixelates the image.
 907      *
 908      * @param  int  $blockSize Block size in pixels.
 909      * @param  bool $advanced  Set to true to enable advanced pixelation.
 910      * @return Image
 911      */
 912     public function pixelate($blockSize = 3, $advanced = false)
 913     {
 914         imagefilter(
 915             $this->image,
 916             IMG_FILTER_PIXELATE,
 917             Normalize::fitInRange($blockSize, 1),
 918             $advanced
 919         );
 920 
 921         return $this;
 922     }
 923 
 924     /**
 925      * A combination of various effects to achieve a sepia like effect.
 926      *
 927      * TODO: Create an additional class with instagram-like effects and move it there.
 928      *
 929      * @param  int   $alpha Defines the transparency of the effect: from 0 to 100
 930      * @return Image
 931      */
 932     public function sepia($alpha = 0)
 933     {
 934         return $this
 935             ->grayscale()
 936             ->contrast(-3)
 937             ->brightness(-15)
 938             ->colorize([
 939                 'r' => 100,
 940                 'g' => 70,
 941                 'b' => 50,
 942                 'a' => Normalize::fitInRange($alpha, 0, 100)
 943             ])
 944         ;
 945     }
 946 
 947     /**
 948      * Makes the image smoother.
 949      *
 950      * @param  int   $level Level of smoothness, between -15 and 15.
 951      * @return Image
 952      */
 953     public function smooth($level)
 954     {
 955         imagefilter(
 956             $this->image,
 957             IMG_FILTER_SMOOTH,
 958             Normalize::fitInRange($level, -15, 15)
 959         );
 960 
 961         return $this;
 962     }
 963 
 964     /**
 965      * Adds a vignette to image.
 966      *
 967      * @param  float  $size  Size of the vignette, between 0 and 10. Low is sharper.
 968      * @param  float  $level Vignete transparency, between 0 and 1
 969      * @return Image
 970      * @link   http://php.net/manual/en/function.imagefilter.php#109809
 971      */
 972     public function vignette($size = 0.7, $level = 0.8)
 973     {
 974         for ($x = 0; $x < $this->width; ++$x) {
 975             for ($y = 0; $y < $this->height; ++$y) {
 976                 $index = imagecolorat($this->image, $x, $y);
 977                 $rgb = imagecolorsforindex($this->image, $index);
 978 
 979                 $this->vignetteEffect($size, $level, $x, $y, $rgb);
 980                 $color = imagecolorallocate($this->image, $rgb['red'], $rgb['green'], $rgb['blue']);
 981 
 982                 imagesetpixel($this->image, $x, $y, $color);
 983             }
 984         }
 985 
 986         return $this;
 987     }
 988 
 989     /**
 990      * Sets quality for gif and jpg files.
 991      *
 992      * @param int $quality A value from 0 (zero quality) to 100 (max quality).
 993      * @return Image
 994      * @codeCoverageIgnore
 995      */
 996     public function setQuality($quality)
 997     {
 998         $this->quality = $quality;
 999 
1000         return $this;
1001     }
1002 
1003     /**
1004      * Sets compression for png files.
1005      *
1006      * @param int $compression A value from 0 (no compression, not recommended) to 9.
1007      * @return Image
1008      * @codeCoverageIgnore
1009      */
1010     public function setCompression($compression)
1011     {
1012         $this->compression = $compression;
1013 
1014         return $this;
1015     }
1016 
1017     /**
1018      * Allows you to set the current image resource.
1019      *
1020      * This is intented for use it in conjuntion with getImage.
1021      *
1022      * @param resource $image Image resource to be set.
1023      * @throws Exception      If given image is not a GD resource.
1024      * @return Image
1025      */
1026     public function setImage($image)
1027     {
1028         if (!is_resource($image) || !get_resource_type($image) == 'gd') {
1029             throw new Exception("Given image is not a GD image resource");
1030         }
1031 
1032         $this->image = $image;
1033         $this->updateSize();
1034 
1035         return $this;
1036     }
1037 
1038     /**
1039      * Useful method to calculate real crop measures. Used when you crop an image
1040      * which is smaller than the original one. In those cases you can call
1041      * calculateCropMeasures to retrieve the real $ox, $oy, $dx & $dy of the
1042      * image to be cropped.
1043      *
1044      * Note that you need to set the destiny image and pass the smaller (cropped)
1045      * image to this function.
1046      *
1047      * @param  string|Image $croppedFile The cropped image.
1048      * @param  mixed        $ox          Origin X.
1049      * @param  int          $oy          Origin Y.
1050      * @param  int          $dx          Destiny X.
1051      * @param  int          $dy          Destiny Y.
1052      * @return array
1053      */
1054     public function calculateCropMeasures($croppedFile, $ox, $oy = null, $dx = null, $dy = null)
1055     {
1056         list($ox, $oy, $dx, $dy) = Normalize::cropMeasures($ox, $oy, $dx, $dy);
1057 
1058         if (!($croppedFile instanceof self)) {
1059             $croppedFile = new self($croppedFile);
1060         }
1061 
1062         $meta = $croppedFile->getMetadata();
1063 
1064         $rateWidth = $this->width / $meta['width'];
1065         $rateHeight = $this->height / $meta['height'];
1066 
1067         $ox = round($ox * $rateWidth);
1068         $oy = round($oy * $rateHeight);
1069         $dx = round($dx * $rateHeight);
1070         $dy = round($dy * $rateHeight);
1071 
1072         $width = $dx - $ox;
1073         $height = $dy - $oy;
1074 
1075         return [$ox, $oy, $dx, $dy, $width, $height];
1076     }
1077 
1078     /**
1079      * Returns image resource, so you can use it however you wan.
1080      *
1081      * @return resource
1082      * @codeCoverageIgnore
1083      */
1084     public function getImage()
1085     {
1086         return $this->image;
1087     }
1088 
1089     /**
1090      * Returns metadata for current image.
1091      *
1092      * @return array
1093      * @codeCoverageIgnore
1094      */
1095     public function getMetadata()
1096     {
1097         return $this->metadata;
1098     }
1099 
1100     /**
1101      * Gets metadata information from given $filename.
1102      *
1103      * @param  string $filename File path
1104      * @return array
1105      */
1106     public static function getMetadataFromFile($filename)
1107     {
1108         $info = getimagesize($filename);
1109 
1110         $metadata = [
1111             'width'  => $info[0],
1112             'height' => $info[1],
1113             'mime'   => $info['mime'],
1114             'exif'   => null // set later, if necessary
1115         ];
1116 
1117         if (function_exists('exif_read_data') && $metadata['mime'] == 'image/jpeg') {
1118             $metadata['exif'] = @exif_read_data($filename);
1119         }
1120 
1121         return $metadata;
1122     }
1123 
1124     /**
1125      * Loads metadata to internal variables.
1126      *
1127      * @return void
1128      * @codeCoverageIgnore
1129      */
1130     protected function getMetadataForImage()
1131     {
1132         $this->metadata = $this->getMetadataFromFile($this->filename);
1133 
1134         $this->width = $this->metadata['width'];
1135         $this->height = $this->metadata['height'];
1136     }
1137 
1138     /**
1139      * Gets mime for an image from its extension.
1140      *
1141      * @param  string $filename Filename to be checked.
1142      * @return string           Mime for the filename given.
1143      * @throws InvalidExtensionException
1144      */
1145     protected function getMimeFromExtension($filename)
1146     {
1147         $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
1148 
1149         switch ($extension) {
1150             case 'jpg':
1151             case 'jpeg':
1152                 return 'image/jpeg';
1153             case 'png':
1154                 return 'image/png';
1155             case 'gif':
1156                 return 'image/gif';
1157             default:
1158                 throw new InvalidExtensionException($extension);
1159         }
1160     }
1161 
1162     /**
1163      * Updates current image metadata.
1164      *
1165      * @return void
1166      * @codeCoverageIgnore
1167      */
1168     protected function updateMetadata()
1169     {
1170         $this->metadata['width'] = $this->width;
1171         $this->metadata['height'] = $this->height;
1172     }
1173 
1174     /**
1175      * Resets width and height of the current image.
1176      *
1177      * @return void
1178      * @codeCoverageIgnore
1179      */
1180     protected function updateSize()
1181     {
1182         $this->width  = imagesx($this->image);
1183         $this->height = imagesy($this->image);
1184 
1185         $this->updateMetadata();
1186     }
1187 
1188     /**
1189      * Required by vignette to generate the propper colors.
1190      *
1191      * @param  float  $size  Size of the vignette, between 0 and 10. Low is sharper.
1192      * @param  float  $level Vignete transparency, between 0 and 1
1193      * @param  int    $x     X position of the pixel.
1194      * @param  int    $y     Y position of the pixel.
1195      * @param  array  &$rgb  Current pixel olor information.
1196      * @return void
1197      * @codeCoverageIgnore
1198      */
1199     protected function vignetteEffect($size, $level, $x, $y, &$rgb)
1200     {
1201         $l = sin(M_PI / $this->width * $x) * sin(M_PI / $this->height * $y);
1202         $l = pow($l, Normalize::fitInRange($size, 0, 10));
1203 
1204         $l = 1 - Normalize::fitInRange($level, 0, 1) * (1 - $l);
1205 
1206         $rgb['red'] *= $l;
1207         $rgb['green'] *= $l;
1208         $rgb['blue'] *= $l;
1209     }
1210 }
1211 
Watimage API API documentation generated by ApiGen