Просмотр исходного кода

#141 images engine based on Intervention Image

Alex Trofimov 11 лет назад
Родитель
Сommit
e67cb012d1
100 измененных файлов с 6385 добавлено и 499 удалено
  1. 122 450
      inc/classes/BxDolImageResize.php
  2. 12 47
      inc/classes/BxDolTranscoder.php
  3. 0 2
      inc/classes/BxDolTranscoderImage.php
  4. 2 0
      modules/boonex/albums/classes/BxAlbumsModule.php
  5. 226 0
      plugins/intervention-image/Intervention/Image/AbstractColor.php
  6. 270 0
      plugins/intervention-image/Intervention/Image/AbstractDecoder.php
  7. 132 0
      plugins/intervention-image/Intervention/Image/AbstractDriver.php
  8. 218 0
      plugins/intervention-image/Intervention/Image/AbstractEncoder.php
  9. 246 0
      plugins/intervention-image/Intervention/Image/AbstractFont.php
  10. 71 0
      plugins/intervention-image/Intervention/Image/AbstractShape.php
  11. 79 0
      plugins/intervention-image/Intervention/Image/Commands/AbstractCommand.php
  12. 225 0
      plugins/intervention-image/Intervention/Image/Commands/Argument.php
  13. 29 0
      plugins/intervention-image/Intervention/Image/Commands/ChecksumCommand.php
  14. 35 0
      plugins/intervention-image/Intervention/Image/Commands/CircleCommand.php
  15. 36 0
      plugins/intervention-image/Intervention/Image/Commands/EllipseCommand.php
  16. 37 0
      plugins/intervention-image/Intervention/Image/Commands/ExifCommand.php
  17. 62 0
      plugins/intervention-image/Intervention/Image/Commands/IptcCommand.php
  18. 36 0
      plugins/intervention-image/Intervention/Image/Commands/LineCommand.php
  19. 48 0
      plugins/intervention-image/Intervention/Image/Commands/OrientateCommand.php
  20. 48 0
      plugins/intervention-image/Intervention/Image/Commands/PolygonCommand.php
  21. 36 0
      plugins/intervention-image/Intervention/Image/Commands/RectangleCommand.php
  22. 26 0
      plugins/intervention-image/Intervention/Image/Commands/ResponseCommand.php
  23. 34 0
      plugins/intervention-image/Intervention/Image/Commands/TextCommand.php
  24. 91 0
      plugins/intervention-image/Intervention/Image/Constraint.php
  25. 8 0
      plugins/intervention-image/Intervention/Image/Exception/InvalidArgumentException.php
  26. 8 0
      plugins/intervention-image/Intervention/Image/Exception/MissingDependencyException.php
  27. 8 0
      plugins/intervention-image/Intervention/Image/Exception/NotFoundException.php
  28. 8 0
      plugins/intervention-image/Intervention/Image/Exception/NotReadableException.php
  29. 8 0
      plugins/intervention-image/Intervention/Image/Exception/NotSupportedException.php
  30. 8 0
      plugins/intervention-image/Intervention/Image/Exception/NotWritableException.php
  31. 8 0
      plugins/intervention-image/Intervention/Image/Exception/RuntimeException.php
  32. 13 0
      plugins/intervention-image/Intervention/Image/Facades/Image.php
  33. 78 0
      plugins/intervention-image/Intervention/Image/File.php
  34. 42 0
      plugins/intervention-image/Intervention/Image/Filters/DemoFilter.php
  35. 14 0
      plugins/intervention-image/Intervention/Image/Filters/FilterInterface.php
  36. 226 0
      plugins/intervention-image/Intervention/Image/Gd/Color.php
  37. 28 0
      plugins/intervention-image/Intervention/Image/Gd/Commands/BackupCommand.php
  38. 23 0
      plugins/intervention-image/Intervention/Image/Gd/Commands/BlurCommand.php
  39. 19 0
      plugins/intervention-image/Intervention/Image/Gd/Commands/BrightnessCommand.php
  40. 27 0
      plugins/intervention-image/Intervention/Image/Gd/Commands/ColorizeCommand.php
  41. 19 0
      plugins/intervention-image/Intervention/Image/Gd/Commands/ContrastCommand.php
  42. 40 0
      plugins/intervention-image/Intervention/Image/Gd/Commands/CropCommand.php
  43. 25 0
      plugins/intervention-image/Intervention/Image/Gd/Commands/DestroyCommand.php
  44. 68 0
      plugins/intervention-image/Intervention/Image/Gd/Commands/FillCommand.php
  45. 33 0
      plugins/intervention-image/Intervention/Image/Gd/Commands/FitCommand.php
  46. 37 0
      plugins/intervention-image/Intervention/Image/Gd/Commands/FlipCommand.php
  47. 19 0
      plugins/intervention-image/Intervention/Image/Gd/Commands/GammaCommand.php
  48. 24 0
      plugins/intervention-image/Intervention/Image/Gd/Commands/GetSizeCommand.php
  49. 17 0
      plugins/intervention-image/Intervention/Image/Gd/Commands/GreyscaleCommand.php
  50. 28 0
      plugins/intervention-image/Intervention/Image/Gd/Commands/HeightenCommand.php
  51. 32 0
      plugins/intervention-image/Intervention/Image/Gd/Commands/InsertCommand.php
  52. 21 0
      plugins/intervention-image/Intervention/Image/Gd/Commands/InterlaceCommand.php
  53. 17 0
      plugins/intervention-image/Intervention/Image/Gd/Commands/InvertCommand.php
  54. 52 0
      plugins/intervention-image/Intervention/Image/Gd/Commands/LimitColorsCommand.php
  55. 81 0
      plugins/intervention-image/Intervention/Image/Gd/Commands/MaskCommand.php
  56. 29 0
      plugins/intervention-image/Intervention/Image/Gd/Commands/OpacityCommand.php
  57. 36 0
      plugins/intervention-image/Intervention/Image/Gd/Commands/PickColorCommand.php
  58. 24 0
      plugins/intervention-image/Intervention/Image/Gd/Commands/PixelCommand.php
  59. 19 0
      plugins/intervention-image/Intervention/Image/Gd/Commands/PixelateCommand.php
  60. 35 0
      plugins/intervention-image/Intervention/Image/Gd/Commands/ResetCommand.php
  61. 84 0
      plugins/intervention-image/Intervention/Image/Gd/Commands/ResizeCanvasCommand.php
  62. 82 0
      plugins/intervention-image/Intervention/Image/Gd/Commands/ResizeCommand.php
  63. 26 0
      plugins/intervention-image/Intervention/Image/Gd/Commands/RotateCommand.php
  64. 32 0
      plugins/intervention-image/Intervention/Image/Gd/Commands/SharpenCommand.php
  65. 176 0
      plugins/intervention-image/Intervention/Image/Gd/Commands/TrimCommand.php
  66. 28 0
      plugins/intervention-image/Intervention/Image/Gd/Commands/WidenCommand.php
  67. 132 0
      plugins/intervention-image/Intervention/Image/Gd/Decoder.php
  68. 86 0
      plugins/intervention-image/Intervention/Image/Gd/Driver.php
  69. 105 0
      plugins/intervention-image/Intervention/Image/Gd/Encoder.php
  70. 255 0
      plugins/intervention-image/Intervention/Image/Gd/Font.php
  71. 40 0
      plugins/intervention-image/Intervention/Image/Gd/Shapes/CircleShape.php
  72. 64 0
      plugins/intervention-image/Intervention/Image/Gd/Shapes/EllipseShape.php
  73. 89 0
      plugins/intervention-image/Intervention/Image/Gd/Shapes/LineShape.php
  74. 48 0
      plugins/intervention-image/Intervention/Image/Gd/Shapes/PolygonShape.php
  75. 75 0
      plugins/intervention-image/Intervention/Image/Gd/Shapes/RectangleShape.php
  76. 372 0
      plugins/intervention-image/Intervention/Image/Image.php
  77. 128 0
      plugins/intervention-image/Intervention/Image/ImageManager.php
  78. 87 0
      plugins/intervention-image/Intervention/Image/ImageManagerStatic.php
  79. 81 0
      plugins/intervention-image/Intervention/Image/ImageServiceProvider.php
  80. 100 0
      plugins/intervention-image/Intervention/Image/ImageServiceProviderLaravel4.php
  81. 135 0
      plugins/intervention-image/Intervention/Image/ImageServiceProviderLaravel5.php
  82. 277 0
      plugins/intervention-image/Intervention/Image/Imagick/Color.php
  83. 22 0
      plugins/intervention-image/Intervention/Image/Imagick/Commands/BackupCommand.php
  84. 19 0
      plugins/intervention-image/Intervention/Image/Imagick/Commands/BlurCommand.php
  85. 19 0
      plugins/intervention-image/Intervention/Image/Imagick/Commands/BrightnessCommand.php
  86. 42 0
      plugins/intervention-image/Intervention/Image/Imagick/Commands/ColorizeCommand.php
  87. 19 0
      plugins/intervention-image/Intervention/Image/Imagick/Commands/ContrastCommand.php
  88. 43 0
      plugins/intervention-image/Intervention/Image/Imagick/Commands/CropCommand.php
  89. 25 0
      plugins/intervention-image/Intervention/Image/Imagick/Commands/DestroyCommand.php
  90. 103 0
      plugins/intervention-image/Intervention/Image/Imagick/Commands/FillCommand.php
  91. 41 0
      plugins/intervention-image/Intervention/Image/Imagick/Commands/FitCommand.php
  92. 25 0
      plugins/intervention-image/Intervention/Image/Imagick/Commands/FlipCommand.php
  93. 19 0
      plugins/intervention-image/Intervention/Image/Imagick/Commands/GammaCommand.php
  94. 26 0
      plugins/intervention-image/Intervention/Image/Imagick/Commands/GetSizeCommand.php
  95. 17 0
      plugins/intervention-image/Intervention/Image/Imagick/Commands/GreyscaleCommand.php
  96. 28 0
      plugins/intervention-image/Intervention/Image/Imagick/Commands/HeightenCommand.php
  97. 31 0
      plugins/intervention-image/Intervention/Image/Imagick/Commands/InsertCommand.php
  98. 27 0
      plugins/intervention-image/Intervention/Image/Imagick/Commands/InterlaceCommand.php
  99. 17 0
      plugins/intervention-image/Intervention/Image/Imagick/Commands/InvertCommand.php
  100. 57 0
      plugins/intervention-image/Intervention/Image/Imagick/Commands/LimitColorsCommand.php

+ 122 - 450
inc/classes/BxDolImageResize.php

@@ -7,38 +7,27 @@
  * @{
  */
 
-// Function error codes
-define( 'IMAGE_ERROR_SUCCESS',               0 );
-define( 'IMAGE_ERROR_SOURCE_NOT_EXISTS',     1 );
-define( 'IMAGE_ERROR_WRONG_TYPE',            2 );
-define( 'IMAGE_ERROR_FILE_OPEN_FAILED',      3 );
-define( 'IMAGE_ERROR_IMAGEMAGICK_ERROR',     4 );
-define( 'IMAGE_ERROR_GD_NOT_INSTALLED',      5 );
-define( 'IMAGE_ERROR_GD_TYPE_NOT_SUPPORTED', 6 );
-define( 'IMAGE_ERROR_GD_OPEN_FAILED',        7 );
-define( 'IMAGE_ERROR_GD_RESIZE_ERROR',       8 );
-define( 'IMAGE_ERROR_GD_MERGE_ERROR',        9 );
-define( 'IMAGE_ERROR_GD_WRITE_FAILED',       10 );
-define( 'IMAGE_ERROR_GD_TTF_NOT_SUPPORTED',  11 );
-define( 'IMAGE_ERROR_GD_FILTER_ERROR',       12 );
-
-// Image types for GD
-// NOTE: actually these constants exist in PHP >= 4.3.0, but they are included for
-//       back compatibility
-define( 'IMAGE_TYPE_GIF',         1 );
-define( 'IMAGE_TYPE_JPG',         2 );
-define( 'IMAGE_TYPE_PNG',         3 );
+spl_autoload_register(function ($sClass) {
+    // autoload Intervention Image files
+    if (0 == strncmp('Intervention', $sClass, 12)) {
+        $sFile = BX_DIRECTORY_PATH_PLUGINS . 'intervention-image/' . str_replace('\\', '/', $sClass) . '.php';
+        if (file_exists($sFile))
+            require_once($sFile);
+    }
+});
+
+define('IMAGE_ERROR_SUCCESS', 0); ///< operation was successfull
+define('IMAGE_ERROR_WRONG_TYPE', 2); ///< operation failed, most probably because incorrect image format(or not image file) was provided
 
 class BxDolImageResize extends BxDol implements iBxDolSingleton
 {
     protected $w = 64, $h = 64; ///< size of destination image
-    protected $_isCrop = false;
     protected $_isAutoCrop = false;
-    protected $_cropX, $_cropY, $_cropW, $_cropH;
-    protected $_iForceOutputType = false; ///< force IMAGE_TYPE_PNG, IMAGE_TYPE_PNG, IMAGE_TYPE_GIF or false to keep the original format // TODO: check if this setting works when ImageMagick is used
     protected $_iJpegQuality = 90; ///< jpeg quality
     protected $_isSquareResize = false; ///< use smart resize, destination image will be exact Width x Height size
     protected $_isUseGD; ///< use GD library or command line ImagMagic utilites
+    protected $_oManager; ///< Intervention Image Manager
+    protected $_sError; ///< Intervention Image Manager error string
 
     function __construct()
     {
@@ -48,6 +37,8 @@ class BxDolImageResize extends BxDol implements iBxDolSingleton
         parent::__construct();
 
         $this->_isUseGD = getParam('enable_gd') == 'on' && extension_loaded('gd') ? true : false;
+
+        $this->_oManager = new Intervention\Image\ImageManager(array('driver' => $this->_isUseGD ? 'gd' : 'imagick'));
     }
 
     /**
@@ -70,16 +61,26 @@ class BxDolImageResize extends BxDol implements iBxDolSingleton
         return $GLOBALS['bxDolClasses'][__CLASS__];
     }
 
-    function isAllowedImage ($sPath)
+    function getManager ()
     {
-        if ($this->_isUseGD) {
-            $aImage = getimagesize($sPath);
-            if (!empty($aImage) && in_array($aImage[2], array(1, 2, 3)))
-                return true;
-        } else {
-            // TODO: image checking using image magick
+        return $this->_oManager;
+    }
+
+    function getError ()
+    {
+        return $this->_sError;
+    }
+
+    function isAllowedImage ($sSrcImage)
+    {
+        try {
+            $this->_oManager->make($sSrcImage);
         }
-        return false;
+        catch (Exception $e) {
+            return false;
+        }
+
+        return true;
     }
 
     function resize ($mixedImage, $sDstImage = '')
@@ -95,16 +96,15 @@ class BxDolImageResize extends BxDol implements iBxDolSingleton
         }
     }
 
-    function applyWatermark ($mixedImage, $sDstImage, $wtrFilename, $wtrTransparency)
+    function applyWatermark ($mixedImage, $sDstImage, $sWtrImage, $iTransparency, $sPosition = 'bottom-right', $sPositionOffsetX = 0, $sPositionOffsetY = 0, $sScaleFactor = 0.2 )
     {
         if (is_array($mixedImage)) {
             $aRet = array();
-            foreach ($mixedImage as $s) {
-                $aRet[] = $this->_applyWatermark ($s, $s, $wtrFilename, $wtrTransparency);
-            }
+            foreach ($mixedImage as $s)
+                $aRet[] = $this->_applyWatermark ($s, $s, $sWtrImage, $iTransparency, $sPosition, $sPositionOffsetX, $sPositionOffsetY, $sScaleFactor);
             return $aRet;
         } else {
-            return $this->_applyWatermark ($mixedImage, $sDstImage, $wtrFilename, $wtrTransparency);
+            return $this->_applyWatermark ($mixedImage, $sDstImage, $sWtrImage, $iTransparency, $sPosition, $sPositionOffsetX, $sPositionOffsetY, $sScaleFactor);
         }
     }
 
@@ -129,19 +129,9 @@ class BxDolImageResize extends BxDol implements iBxDolSingleton
 
     function removeCropOptions ()
     {
-        $this->_isCrop = false;
         $this->_isAutoCrop = false;
     }
 
-    function setCropOptions ($x, $y, $w, $h)
-    {
-        $this->_isCrop = true;
-        $this->_cropX = $x;
-        $this->_cropY = $y;
-        $this->_cropW = $w;
-        $this->_cropH = $h;
-    }
-
     /**
      * Crop image to destination size with filling whole area of destination size
      */
@@ -150,16 +140,6 @@ class BxDolImageResize extends BxDol implements iBxDolSingleton
         $this->_isAutoCrop = $b;
     }
 
-    function setJpegOutput ($b)
-    {
-        $this->setOutputType($b ? IMAGE_TYPE_JPG : false);
-    }
-
-    function setOutputType ($iType)
-    {
-        $this->_iForceOutputType = $iType;;
-    }
-
     function setJpegQuality ($i)
     {
         $this->_iJpegQuality = $i;
@@ -177,424 +157,116 @@ class BxDolImageResize extends BxDol implements iBxDolSingleton
 
     static function getImageSize($sPath)
     {
-        $aSize = getimagesize($sPath);
-        return array ('w' => $aSize[0], 'h' => $aSize[1]);
-    }
-
-    function getAverageColor($sPath)
-    {
-    	return $this->isUsedGD() ? $this->_getAverageColorGD($sPath) : $this->_getAverageColorImageMagic($sPath);
+        $o = self::getInstance();
+        return $o->_getImageSize($sPath);
     }
 
-    // private functions are below -------------------------------
-
-    function _grayscale ($sSrcImage, $sDstImage = '')
+    function _getImageSize($sPath)
     {
-        if (!file_exists($sSrcImage))
-            return IMAGE_ERROR_SOURCE_NOT_EXISTS;
-
-        if (!$sDstImage)
-            $sDstImage = $sSrcImage;
-
-        if ($sDstImage == $sSrcImage) {
-            chmod($sDstImage, 0666);
-        }
-
-        return $this->_isUseGD ? $this->_grayscaleGD ($sSrcImage, $sDstImage) : $this->_grayscaleImageMagick ($sSrcImage, $sDstImage);
-    }
-
-    function _grayscaleGD ($sSrcImage, $sDstImage)
-    {
-        $iErr = 0;
-        $src_im =& $this->_createGDImage($sSrcImage, $size, $iErr);
-
-        if ($iErr)
-            return $iErr;
-        if (!$src_im)
-            return IMAGE_ERROR_GD_OPEN_FAILED;
-
-        if (!imagefilter($src_im, IMG_FILTER_GRAYSCALE))
-            return IMAGE_ERROR_GD_FILTER_ERROR;
-
-        $writeResult = $this->_writeImageGD ($src_im, $sDstImage, $size[2]);
-
-        // free memory
-        imagedestroy( $src_im );
-
-        if ( $writeResult && file_exists($sDstImage) )
-            return IMAGE_ERROR_SUCCESS;
-
-        return IMAGE_ERROR_GD_WRITE_FAILED;
-    }
-
-    function _grayscaleImageMagick ($sSrcImage, $sDstImage)
-    {
-        $cmd = "{$GLOBALS['CONVERT']} \"$sSrcImage\" -type Grayscale -quantize Gray \"$sDstImage\"";
-        @exec( $cmd );
-
-        if ( file_exists($sDstImage) )
-            return IMAGE_ERROR_SUCCESS;
-
-        return IMAGE_ERROR_IMAGEMAGICK_ERROR;
-    }
-
-    function _resize ($sSrcImage, $sDstImage = '')
-    {
-        if (!file_exists($sSrcImage))
-            return IMAGE_ERROR_SOURCE_NOT_EXISTS;
-
-        if (!$sDstImage)
-            $sDstImage = $sSrcImage;
-
-        if ($sDstImage == $sSrcImage) {
-            chmod($sDstImage, 0666);
+        $this->_sError = '';
+        try {        
+            $o = $this->_oManager->make($sPath);
+        } 
+        catch (Exception $e) {
+            $this->_sError = $e->getMessage();
+            return false;
         }
-
-        return $this->_isUseGD ? $this->_resizeGD ($sSrcImage, $sDstImage) : $this->_resizeImageMagick ($sSrcImage, $sDstImage);
+        return array ('w' => $o->width(), 'h' => $o->height());
     }
 
-    function _resizeGD ($sSrcImage, $sDstImage)
+    function getAverageColor($sSrcImage)
     {
-        $iErr = 0;
-        $src_im =& $this->_createGDImage($sSrcImage, $size, $iErr);
-        $sizeOrig = $size;
-
-        if ($iErr)
-            return $iErr;
-        if (!$src_im)
-            return IMAGE_ERROR_GD_OPEN_FAILED;
-
-        $xd = $yd = 0;
-        $xs = $ys = 0;
-
-        if ($this->_isAutoCrop) {
-            $sourceRatio = (float) ($size[0] / $size[1]);
-            $destRatio = (float) ($this->w / $this->h);
-            if ( $sourceRatio > $destRatio )
-                $resizeRatio = (float) ($this->h / $size[1]);
-            else
-                $resizeRatio = (float) ($this->w / $size[0]);
-            $destW = (int) ($resizeRatio * $size[0]);
-            $destH = (int) ($resizeRatio * $size[1]);
-
-            if ($destW > $this->w)
-                $this->setCropOptions (floor(($size[0] - $this->w/$resizeRatio)/2.0), 0, floor($this->w/$resizeRatio), $size[1]);
-            elseif ($destH > $this->h)
-                $this->setCropOptions (0, floor(($size[1] - $this->h/$resizeRatio)/2.0), $size[0], floor($this->h/$resizeRatio));
-        }
-
-        if ($this->_isCrop) {
-            $size[0] = $this->_cropW;
-            $size[1] = $this->_cropH;
-
-            $xs = $this->_cropX;
-            $ys = $this->_cropY;
-
-            $destW = $this->w;
-            $destH = $this->h;
-
-        } elseif ($this->_isSquareResize) {
-
-            $destW = $this->w;
-            $destH = $this->h;
-
-            if ($size[0] < $size[1]) {
-
-                $d = ($size[1] - $size[0])/2;
-                $size[1] = $size[0];
-
-                $xs = 0;
-                $ys = (int)$d;
-
-            } else {
-
-                $d = ($size[0] - $size[1])/2;
-                $size[0] = $size[1];
-
-                $xs = (int)$d;
-                $ys = 0;
-
-            }
-
-        } else {
-
-            // determ destination size
-            $sourceRatio = (float) ($size[0] / $size[1]);
-            $destRatio = (float) ($this->w / $this->h);
-            if ( $sourceRatio > $destRatio )
-                $resizeRatio = (float) ($this->w / $size[0]);
-            else
-                $resizeRatio = (float) ($this->h / $size[1]);
-            $destW = (int) ($resizeRatio * $size[0]);
-            $destH = (int) ($resizeRatio * $size[1]);
-
+        $this->_sError = '';
+        try {
+            $a = $this->_oManager
+                ->make($sSrcImage)
+                ->resize(1, 1)
+                ->pickColor(0, 0, 'array');
+
+            return array('r' => $a[0], 'g' => $a[1], 'b' => $a[2]);
         }
-
-        // this is more qualitative function, but it doesn't exist in old GD and doesn't support GIF format
-        if ( function_exists( 'imagecreatetruecolor' ) && $size[2] != IMAGE_TYPE_GIF ) {
-            // resize only if size is larger than needed
-            if ( $this->_isCrop || $sizeOrig[0] > $this->w || $sizeOrig[1] > $this->h ) {
-                $dst_im = imagecreatetruecolor( $destW, $destH );
-                imagecolortransparent($dst_im, imagecolorallocate($dst_im, 0, 0, 0));
-                imagealphablending($dst_im, false);
-                imagesavealpha($dst_im, true);
-                $convertResult = imagecopyresampled( $dst_im, $src_im, $xd, $yd, $xs, $ys,
-                    $destW, $destH, $size[0], $size[1] );
-            } else {
-                $dst_im = $src_im;
-                $convertResult = true;
-            }
-        } else { // this is for old GD versions and for GIF images
-            // resize only if size is larger than needed
-            if ( $size[0] > $this->w || $size[1] > $this->h ) {
-                $dst_im = imagecreate( $destW, $destH );
-                if ($size[2] == IMAGE_TYPE_GIF) {
-                    ImageColorTransparent( $dst_im, imagecolorallocate($dst_im, 0, 0, 0) );
-                    imagealphablending( $dst_im, false );
-                }
-                $convertResult = imagecopyresized( $dst_im, $src_im, $xd, $yd, $xs, $ys,
-                    $destW, $destH, $size[0], $size[1] );
-            } else {
-                $dst_im = $src_im;
-                $convertResult = true;
-            }
-        }
-
-        if ( !$convertResult )
-            return IMAGE_ERROR_GD_RESIZE_ERROR;
-
-        $writeResult = $this->_writeImageGD ($dst_im, $sDstImage, $size[2]);
-
-        // free memory
-        if ( $dst_im != $src_im ) {
-            imagedestroy( $src_im );
-            imagedestroy( $dst_im );
-        } else {
-            imagedestroy( $src_im );
+        catch (Exception $e) {
+            $this->_sError = $e->getMessage();
+            return false;
         }
-
-        if ( $writeResult && file_exists($sDstImage) )
-            return IMAGE_ERROR_SUCCESS;
-
-        return IMAGE_ERROR_GD_WRITE_FAILED;
     }
 
-    function _resizeImageMagick ($sSrcImage, $sDstImage)
-    {
-        // TODO: $this->_isCrop and $this->_isSquareResize
-        if ( $sSrcImage == $sDstImage ) {
-            $cmd = "{$GLOBALS['MOGRIFY']} -geometry {$this->w}\">\"x{$this->h}\">\" \"$sSrcImage\"";
-            @exec( $cmd );
-            $nameWithoutExt = substr( $sSrcImage, 0, strrpos($sSrcImage, '.') );
-            if ( file_exists( "{$nameWithoutExt}.mgk" ) )
-                rename( "{$nameWithoutExt}.mgk", $sSrcImage );
-        } else {
-            $cmd = "{$GLOBALS['CONVERT']} \"$sSrcImage\" -geometry {$this->w}\">\"x{$this->h}\">\" \"$sDstImage\"";
-            @exec( $cmd );
-        }
-
-        if ( file_exists($sDstImage) )
-            return IMAGE_ERROR_SUCCESS;
-
-        return IMAGE_ERROR_IMAGEMAGICK_ERROR;
-    }
+    // private functions are below -------------------------------
 
-    function _applyWatermark( $srcFilename, $dstFilename, $wtrFilename, $wtrTransparency )
+    function _grayscale ($sSrcImage, $sDstImage = '')
     {
-        // TODO: watermard are not applied correctly, becuase images of different suzes are used, FIX IT
-
-        // input validation
-        $wtrTransparency = (int) $wtrTransparency;
-        if ( $wtrTransparency > 100 )
-            $wtrTransparency = 100;
-        if ( !file_exists( $srcFilename ) )
-            return IMAGE_ERROR_SOURCE_NOT_EXISTS;
-        if ( !file_exists( $wtrFilename ) )
-            return IMAGE_ERROR_SOURCE_NOT_EXISTS;
-
-        // if destination and source filenames are equivalent then change mode for destination
-        if ( $srcFilename == $dstFilename )
-            chmod( $dstFilename, 0666 );
-
-        if ( $this->_isUseGD ) {
-            return $this->_applyWatermarkGD($srcFilename, $dstFilename, $wtrFilename, $wtrTransparency);
-        } else {
-            return $this->_applyWatermarkImageMagick($srcFilename, $dstFilename, $wtrFilename, $wtrTransparency);
+        $this->_sError = '';
+        try {
+            $this->_oManager
+                ->make($sSrcImage)
+                ->greyscale()
+                ->save($sDstImage ? $sDstImage : $sSrcImage, $this->_iJpegQuality);
+
+            chmod($sDstImage ? $sDstImage : $sSrcImage, BX_DOL_FILE_RIGHTS);
         }
-    }
-
-    function _applyWatermarkGD($srcFilename, $dstFilename, $wtrFilename, $wtrTransparency)
-    {
-        $iErr = 0;
-
-        $src_im =& $this->_createGDImage($srcFilename, $size, $iErr);
-        if ($iErr)
-            return $iErr;
-        if (!$src_im)
-            return IMAGE_ERROR_GD_OPEN_FAILED;
-
-        $wtr_im =& $this->_createGDImage($wtrFilename, $wtrSize, $iErr);
-        if ($iErr)
-            return $iErr;
-
-        if (!$wtr_im )
-            return IMAGE_ERROR_GD_OPEN_FAILED;
-
-        if (function_exists('imagecreatetruecolor'))
-            $dst_im = imagecreatetruecolor( $size[0], $size[1] );
-        else
-            $dst_im = imagecreate( $size[0], $size[1] );
-
-        $watermarkX = ($size[0] - $wtrSize[0]) / 2;
-        $watermarkY = $size[1] - $wtrSize[1];
-
-        $copyResult = imagecopy( $dst_im, $src_im, 0, 0, 0, 0, $size[0], $size[1]);
-        if (!$copyResult)
-            return IMAGE_ERROR_GD_MERGE_ERROR;
-
-        imagealphablending($wtr_im, false);
-        imagesavealpha($wtr_im, true);
-
-        if (IMAGE_TYPE_PNG == $wtrSize[2] && imageistruecolor($wtr_im))
-            $mergeResult = imagecopy  ( $dst_im, $wtr_im, $watermarkX, $watermarkY, 0, 0, $wtrSize[0], $wtrSize[1]);
-        else
-            $mergeResult = imagecopymerge  ( $dst_im, $wtr_im, $watermarkX, $watermarkY, 0, 0, $wtrSize[0], $wtrSize[1], $wtrTransparency );
-
-        if ( !$mergeResult )
-            return IMAGE_ERROR_GD_MERGE_ERROR;
-
-        switch ( $size[2] ) {
-            case IMAGE_TYPE_GIF:
-                $writeResult = imagegif( $dst_im, $dstFilename );
-                break;
-            case IMAGE_TYPE_JPG:
-                $writeResult = imagejpeg( $dst_im, $dstFilename, $this->_iJpegQuality);
-                break;
-            case IMAGE_TYPE_PNG:
-                $writeResult = imagepng( $dst_im, $dstFilename );
-                break;
+        catch (Exception $e) {
+            $this->_sError = $e->getMessage();
+            return IMAGE_ERROR_WRONG_TYPE;
         }
 
-        // free memory
-        imagedestroy( $dst_im );
-        imagedestroy( $src_im );
-        imagedestroy( $wtr_im );
-
-        if ( $writeResult )
-            return IMAGE_ERROR_SUCCESS;
-        else
-            return IMAGE_ERROR_GD_WRITE_FAILED;
-    }
-
-    function _applyWatermarkImageMagick($srcFilename, $dstFilename, $wtrFilename, $wtrTransparency)
-    {
-        $imgTransparency = 100 - $wtrTransparency;
-        $cmd = "{$GLOBALS['COMPOSITE']} -gravity \"South\" -dissolve \"$imgTransparency\" \"$wtrFilename\" -dissolve $wtrTransparency \"$srcFilename\" \"$dstFilename\"";
-        @exec( $cmd );
-
-        if ( file_exists($dstFilename) )
-            return IMAGE_ERROR_SUCCESS;
-
-        return IMAGE_ERROR_IMAGEMAGICK_ERROR;
+        return IMAGE_ERROR_SUCCESS;
     }
 
-    function & _createGDImage($s, &$aSize, &$iErr) {
-
-        if (!isset($this->gdInfoArray))
-            $this->gdInfoArray = gd_info();
-
-        $aSize = getimagesize($s);
-
-        // only GIF, JPG and PNG allowed
-        switch ( $aSize[2] ) {
-            case IMAGE_TYPE_GIF:
-                if (!isset($this->gdInfoArray['GIF Read Support']) || !$this->gdInfoArray['GIF Read Support'] || !isset($this->gdInfoArray['GIF Create Support']) || !$this->gdInfoArray['GIF Create Support'])
-                    return ($iErr = IMAGE_ERROR_GD_TYPE_NOT_SUPPORTED);
-                $r = imagecreatefromgif( $s );
-                return $r;
-            case IMAGE_TYPE_JPG:
-                if ((!isset($this->gdInfoArray['JPG Support']) || !$this->gdInfoArray['JPG Support']) && (!isset($this->gdInfoArray['JPEG Support']) || !$this->gdInfoArray['JPEG Support']))
-                    return ($iErr = IMAGE_ERROR_GD_TYPE_NOT_SUPPORTED);
-                $r = imagecreatefromjpeg( $s );
-                return $r;
-            case IMAGE_TYPE_PNG:
-                if (!isset($this->gdInfoArray['PNG Support']) || !$this->gdInfoArray['PNG Support'])
-                    return ($iErr = IMAGE_ERROR_GD_TYPE_NOT_SUPPORTED);
-                $r = imagecreatefrompng( $s );
-                imagesavealpha($r, true);
-                return $r;
-            default:
-                return ($iErr = IMAGE_ERROR_WRONG_TYPE);
+    function _resize ($sSrcImage, $sDstImage = '')
+    {       
+        $this->_sError = '';
+        try {
+            if ($this->_isAutoCrop || $this->_isSquareResize) {
+                $this->_oManager
+                    ->make($sSrcImage)
+                    ->orientate()
+                    ->fit($this->w, $this->_isSquareResize ? $this->w : $this->h)
+                    ->save($sDstImage ? $sDstImage : $sSrcImage, $this->_iJpegQuality);
+            } 
+            else {
+                $this->_oManager
+                    ->make($sSrcImage)
+                    ->orientate()
+                    ->resize($this->w, $this->h, function ($constraint) {
+                        $constraint->aspectRatio();
+                    })
+                    ->save($sDstImage ? $sDstImage : $sSrcImage, $this->_iJpegQuality);
+            }
+            chmod($sDstImage ? $sDstImage : $sSrcImage, BX_DOL_FILE_RIGHTS);
         }
-    }
-
-    function _writeImageGD ($src_im, $sDstImage, $iSrcImageType)
-    {
-        $writeResult = false;
-
-        switch ($this->_iForceOutputType){
-
-            case IMAGE_TYPE_JPG:
-                $writeResult = imagejpeg( $src_im, $sDstImage, $this->_iJpegQuality);
-                break;
-
-            case IMAGE_TYPE_PNG:
-                $writeResult = imagepng( $src_im, $sDstImage);
-                break;
-
-            case IMAGE_TYPE_GIF:
-                $writeResult = imagegif( $src_im, $sDstImage );
-                break;
-
-            default:
-                switch ($iSrcImageType) {
-                    case IMAGE_TYPE_GIF:
-                        $writeResult = imagegif( $src_im, $sDstImage );
-                        break;
-                    case IMAGE_TYPE_PNG:
-                        $writeResult = imagepng( $src_im, $sDstImage );
-                        break;
-                    case IMAGE_TYPE_JPG:
-                    default:
-                        $writeResult = imagejpeg( $src_im, $sDstImage, $this->_iJpegQuality);
-                        break;
-                }
+        catch (Exception $e) {
+            $this->_sError = $e->getMessage();
+            return IMAGE_ERROR_WRONG_TYPE;
         }
 
-        return $writeResult;
-    }
-
-	function _getAverageColorGD($sPath)
-    {
-    	$iError = 0;
-		$aSize = array();
-		$oImgOrig = $this->_createGDImage($sPath, $aSize, $iError);
-		if($iError != IMAGE_ERROR_SUCCESS)
-			return false;
-
-		$oImgTmp = ImageCreateTrueColor(1,1);
-		ImageCopyResampled($oImgTmp, $oImgOrig, 0, 0, 0, 0, 1, 1, $aSize[0],$aSize[1]);
-		$iRgb = ImageColorAt($oImgTmp, 0, 0);
-
-		$iRed = ($iRgb >> 16) & 0xFF;
-		$iGreen = ($iRgb >> 8) & 0xFF;
-		$iBlue = $iRgb & 0xFF;
-
-		return array('r' => $iRed, 'g' => $iGreen, 'b' => $iBlue);
+        return IMAGE_ERROR_SUCCESS;
     }
 
-    function _getAverageColorImageMagic($sPath)
+    function _applyWatermark( $sSrcImage, $sDstImage, $sWtrImage, $iTransparency, $sPosition = 'bottom-right', $sPositionOffsetX = 0, $sPositionOffsetY = 0, $sScaleFactor = 0.2)
     {
-    	$sCmd = "{$GLOBALS['CONVERT']} $sPath -filter box -resize 1x1! -format \"%[fx:round(255*u.r)],%[fx:round(255*u.g)],%[fx:round(255*u.b)]\" info:";
-        $sResult = @exec($sCmd);
-        if(empty($sResult))
-        	return false;
-
-        $aResult = explode(',', $sResult);
-		if(count($aResult) != 3)
-			return false;
+        $this->_sError = '';
+        try {
+            $oImageOrig = $this->_oManager->make($sSrcImage);
+
+            $oImageOrig
+                ->insert($this->_oManager
+                    ->make($sWtrImage)
+                    ->resize(round($oImageOrig->width() * $sScaleFactor), round($oImageOrig->height() * $sScaleFactor),  function ($constraint) {
+                        $constraint->aspectRatio();
+                        $constraint->upsize();
+                    })
+                    ->opacity($iTransparency), 
+                $sPosition, $sPositionOffsetX, $sPositionOffsetY)
+                ->save($sDstImage ? $sDstImage : $sSrcImage, $this->_iJpegQuality);
+
+            chmod($sDstImage ? $sDstImage : $sSrcImage, BX_DOL_FILE_RIGHTS);
+        }
+        catch (Exception $e) {
+            $this->_sError = $e->getMessage();
+            return IMAGE_ERROR_WRONG_TYPE;
+        }
 
-		return array('r' => $aResult[0], 'g' => $aResult[1], 'b' => $aResult[2]);
+        return IMAGE_ERROR_SUCCESS;        
     }
 }
 

+ 12 - 47
inc/classes/BxDolTranscoder.php

@@ -409,18 +409,18 @@ class BxDolTranscoder extends BxDol implements iBxDolFactoryObject
                         $sExtChange = $aParams['filter_params']['force_type'];
                 }
             }
-        }
 
-        if ($sExtChange) {
-            $iDotPos = strrpos($sTmpFile, '.');
-            $sExtOld = false === $iDotPos ? '' : substr($sTmpFile, $iDotPos+1);
-            if ($sExtOld != $sExtChange) {
-                $sTmpFileOld = $sTmpFile;
-                $sTmpFile = false === $iDotPos ? $sTmpFile . '.' . $sExtChange : substr_replace($sTmpFile, $sExtChange, $iDotPos+1, strlen($sExtOld));
-                if (!rename($sTmpFileOld, $sTmpFile)) {
-                    @unlink($sTmpFileOld);
-                    $this->_oDb->updateQueueStatus($mixedHandler, BX_DOL_QUEUE_FAILED, "change file extension failed: $sTmpFileOld => $sTmpFile");
-                    return false;
+            if ($sExtChange) {
+                $iDotPos = strrpos($sTmpFile, '.');
+                $sExtOld = false === $iDotPos ? '' : substr($sTmpFile, $iDotPos+1);
+                if ($sExtOld != $sExtChange) {
+                    $sTmpFileOld = $sTmpFile;
+                    $sTmpFile = false === $iDotPos ? $sTmpFile . '.' . $sExtChange : substr_replace($sTmpFile, $sExtChange, $iDotPos+1, strlen($sExtOld));
+                    if (!rename($sTmpFileOld, $sTmpFile)) {
+                        @unlink($sTmpFileOld);
+                        $this->_oDb->updateQueueStatus($mixedHandler, BX_DOL_QUEUE_FAILED, "change file extension failed: $sTmpFileOld => $sTmpFile");
+                        return false;
+                    }
                 }
             }
         }
@@ -587,18 +587,7 @@ class BxDolTranscoder extends BxDol implements iBxDolFactoryObject
         $o = BxDolImageResize::getInstance();
         $o->removeCropOptions ();
 
-        // if only one dimension specified - automatically calculate another dimension 
-        if ((isset($aParams['w']) && !isset($aParams['h'])) || (isset($aParams['h']) && !isset($aParams['w']))) {
-            $a = $o->getImageSize ($sFile);
-            $fRatio = $a['w'] / $a['h'];
-            if (isset($aParams['w']))
-                $aParams['h'] = round($aParams['w'] / $fRatio);
-            else
-                $aParams['w'] = round($aParams['h'] * $fRatio);
-        }
-
-        if (isset($aParams['w']) && isset($aParams['h']))
-            $o->setSize ($aParams['w'] * $this->getDevicePixelRatio(), $aParams['h'] * $this->getDevicePixelRatio());
+        $o->setSize (isset($aParams['w']) ? round($aParams['w'] * $this->getDevicePixelRatio()) : null, isset($aParams['h']) ? round($aParams['h'] * $this->getDevicePixelRatio()) : null);
 
         if (isset($aParams['crop_resize']) && $aParams['crop_resize'])
             $o->setAutoCrop (true);
@@ -607,36 +596,12 @@ class BxDolTranscoder extends BxDol implements iBxDolFactoryObject
         else
             $o->setSquareResize (false);
 
-        $this->_checkForceImageType ($o, $aParams);
-
         if (IMAGE_ERROR_SUCCESS == $o->resize($sFile))
             return true;
 
         return false;
     }
 
-    protected function _checkForceImageType ($oImageProcessor, $aParams)
-    {
-        if (empty($aParams['force_type']))
-            $aParams['force_type'] = false;
-
-        switch ($aParams['force_type']) {
-            case 'jpeg':
-            case 'jpg':
-                $oImageProcessor->setOutputType(IMAGE_TYPE_JPG);
-                break;
-            case 'png':
-                $oImageProcessor->setOutputType(IMAGE_TYPE_PNG);
-                break;
-            case 'gif':
-                $oImageProcessor->setOutputType(IMAGE_TYPE_GIF);
-                break;
-            default:
-                $oImageProcessor->setOutputType(false);
-                break;
-        }
-    }
-
     protected function isFileReady_Folder ($mixedHandlerOrig, $isCheckOutdated = true)
     {
         $mixedHandler = $this->processHandlerForRetinaDevice($mixedHandlerOrig);

+ 0 - 2
inc/classes/BxDolTranscoderImage.php

@@ -144,8 +144,6 @@ class BxDolTranscoderImage extends BxDolTranscoder implements iBxDolFactoryObjec
         $o = BxDolImageResize::getInstance();
         $o->removeCropOptions ();
 
-        $this->_checkForceImageType ($o, $aParams);
-
         if (IMAGE_ERROR_SUCCESS == $o->grayscale($sFile))
             return true;
 

+ 2 - 0
modules/boonex/albums/classes/BxAlbumsModule.php

@@ -9,6 +9,8 @@
  * @{
  */
 
+bx_import('BxDolAcl');
+
 /**
  * Albums module
  */

+ 226 - 0
plugins/intervention-image/Intervention/Image/AbstractColor.php

@@ -0,0 +1,226 @@
+<?php
+
+namespace Intervention\Image;
+
+abstract class AbstractColor
+{
+    /**
+     * Initiates color object from integer
+     *
+     * @param  integer $value
+     * @return \Intervention\Image\AbstractColor
+     */
+    abstract public function initFromInteger($value);
+
+    /**
+     * Initiates color object from given array
+     *
+     * @param  array $value
+     * @return \Intervention\Image\AbstractColor
+     */
+    abstract public function initFromArray($value);
+
+    /**
+     * Initiates color object from given string
+     *
+     * @param  string $value
+     * @return \Intervention\Image\AbstractColor
+     */
+    abstract public function initFromString($value);
+
+    /**
+     * Initiates color object from given ImagickPixel object
+     *
+     * @param  ImagickPixel $value
+     * @return \Intervention\Image\AbstractColor
+     */
+    abstract public function initFromObject($value);
+
+    /**
+     * Initiates color object from given R, G and B values
+     *
+     * @param  integer $r
+     * @param  integer $g
+     * @param  integer $b
+     * @return \Intervention\Image\AbstractColor
+     */
+    abstract public function initFromRgb($r, $g, $b);
+
+    /**
+     * Initiates color object from given R, G, B and A values
+     *
+     * @param  integer $r
+     * @param  integer $g
+     * @param  integer $b
+     * @param  float   $a
+     * @return \Intervention\Image\AbstractColor
+     */
+    abstract public function initFromRgba($r, $g, $b, $a);
+
+    /**
+     * Calculates integer value of current color instance
+     *
+     * @return integer
+     */
+    abstract public function getInt();
+
+    /**
+     * Calculates hexadecimal value of current color instance
+     *
+     * @param  string $prefix
+     * @return string
+     */
+    abstract public function getHex($prefix);
+
+    /**
+     * Calculates RGB(A) in array format of current color instance
+     *
+     * @return array
+     */
+    abstract public function getArray();
+
+    /**
+     * Calculates RGBA in string format of current color instance
+     *
+     * @return string
+     */
+    abstract public function getRgba();
+
+    /**
+     * Determines if current color is different from given color
+     *
+     * @param  AbstractColor $color
+     * @param  integer       $tolerance
+     * @return boolean
+     */
+    abstract public function differs(AbstractColor $color, $tolerance = 0);
+
+    /**
+     * Creates new instance
+     *
+     * @param string $value
+     */
+    public function __construct($value = null)
+    {
+        $this->parse($value);
+    }
+
+    /**
+     * Parses given value as color
+     *
+     * @param  mixed $value
+     * @return \Intervention\Image\AbstractColor
+     */
+    public function parse($value)
+    {
+        switch (true) {
+
+            case is_string($value):
+                $this->initFromString($value);
+                break;
+
+            case is_int($value):
+                $this->initFromInteger($value);
+                break;
+
+            case is_array($value):
+                $this->initFromArray($value);
+                break;
+
+            case is_object($value):
+                $this->initFromObject($value);
+                break;
+
+            case is_null($value):
+                $this->initFromArray(array(255, 255, 255, 0));
+                break;
+
+            default:
+                throw new \Intervention\Image\Exception\NotReadableException(
+                    "Color format ({$value}) cannot be read."
+                );
+        }
+
+        return $this;
+    }
+
+    /**
+     * Formats current color instance into given format
+     *
+     * @param  string $type
+     * @return mixed
+     */
+    public function format($type)
+    {
+        switch (strtolower($type)) {
+
+            case 'rgba':
+                return $this->getRgba();
+
+            case 'hex':
+                return $this->getHex('#');
+
+            case 'int':
+            case 'integer':
+                return $this->getInt();
+
+            case 'array':
+                return $this->getArray();
+
+            case 'obj':
+            case 'object':
+                return $this;
+
+            default:
+                throw new \Intervention\Image\Exception\NotSupportedException(
+                    "Color format ({$type}) is not supported."
+                );
+        }
+    }
+
+    /**
+     * Reads RGBA values from string into array
+     *
+     * @param  string $value
+     * @return array
+     */
+    protected function rgbaFromString($value)
+    {
+        $result = false;
+
+        // parse color string in hexidecimal format like #cccccc or cccccc or ccc
+        $hexPattern = '/^#?([a-f0-9]{1,2})([a-f0-9]{1,2})([a-f0-9]{1,2})$/i';
+
+        // parse color string in format rgb(140, 140, 140)
+        $rgbPattern = '/^rgb ?\(([0-9]{1,3}), ?([0-9]{1,3}), ?([0-9]{1,3})\)$/i';
+
+        // parse color string in format rgba(255, 0, 0, 0.5)
+        $rgbaPattern = '/^rgba ?\(([0-9]{1,3}), ?([0-9]{1,3}), ?([0-9]{1,3}), ?([0-9.]{1,4})\)$/i';
+
+        if (preg_match($hexPattern, $value, $matches)) {
+            $result = array();
+            $result[0] = strlen($matches[1]) == '1' ? hexdec($matches[1].$matches[1]) : hexdec($matches[1]);
+            $result[1] = strlen($matches[2]) == '1' ? hexdec($matches[2].$matches[2]) : hexdec($matches[2]);
+            $result[2] = strlen($matches[3]) == '1' ? hexdec($matches[3].$matches[3]) : hexdec($matches[3]);
+            $result[3] = 1;
+        } elseif (preg_match($rgbPattern, $value, $matches)) {
+            $result = array();
+            $result[0] = ($matches[1] >= 0 && $matches[1] <= 255) ? intval($matches[1]) : 0;
+            $result[1] = ($matches[2] >= 0 && $matches[2] <= 255) ? intval($matches[2]) : 0;
+            $result[2] = ($matches[3] >= 0 && $matches[3] <= 255) ? intval($matches[3]) : 0;
+            $result[3] = 1;
+        } elseif (preg_match($rgbaPattern, $value, $matches)) {
+            $result = array();
+            $result[0] = ($matches[1] >= 0 && $matches[1] <= 255) ? intval($matches[1]) : 0;
+            $result[1] = ($matches[2] >= 0 && $matches[2] <= 255) ? intval($matches[2]) : 0;
+            $result[2] = ($matches[3] >= 0 && $matches[3] <= 255) ? intval($matches[3]) : 0;
+            $result[3] = ($matches[4] >= 0 && $matches[4] <= 1) ? $matches[4] : 0;
+        } else {
+            throw new \Intervention\Image\Exception\NotReadableException(
+                "Unable to read color ({$value})."
+            );
+        }
+
+        return $result;
+    }
+}

+ 270 - 0
plugins/intervention-image/Intervention/Image/AbstractDecoder.php

@@ -0,0 +1,270 @@
+<?php
+
+namespace Intervention\Image;
+
+abstract class AbstractDecoder
+{
+    /**
+     * Initiates new image from path in filesystem
+     *
+     * @param  string $path
+     * @return \Intervention\Image\Image
+     */
+    abstract public function initFromPath($path);
+
+    /**
+     * Initiates new image from binary data
+     *
+     * @param  string $data
+     * @return \Intervention\Image\Image
+     */
+    abstract public function initFromBinary($data);
+
+    /**
+     * Initiates new image from GD resource
+     *
+     * @param  Resource $resource
+     * @return \Intervention\Image\Image
+     */
+    abstract public function initFromGdResource($resource);
+
+    /**
+     * Initiates new image from Imagick object
+     *
+     * @param  Imagick $object
+     * @return \Intervention\Image\Image
+     */
+    abstract public function initFromImagick(\Imagick $object);
+
+    /**
+     * Buffer of input data
+     *
+     * @var mixed
+     */
+    private $data;
+
+    /**
+     * Creates new Decoder with data
+     *
+     * @param mixed $data
+     */
+    public function __construct($data = null)
+    {
+        $this->data = $data;
+    }
+
+    /**
+     * Init from fiven URL
+     *
+     * @param  string $url
+     * @return \Intervention\Image\Image
+     */
+    public function initFromUrl($url)
+    {
+        if ($data = @file_get_contents($url)) {
+            return $this->initFromBinary($data);
+        }
+
+        throw new \Intervention\Image\Exception\NotReadableException(
+            "Unable to init from given url (".$url.")."
+        );
+    }
+
+    /**
+     * Determines if current source data is GD resource
+     *
+     * @return boolean
+     */
+    public function isGdResource()
+    {
+        if (is_resource($this->data)) {
+            return (get_resource_type($this->data) == 'gd');
+        }
+
+        return false;
+    }
+
+    /**
+     * Determines if current source data is Imagick object
+     *
+     * @return boolean
+     */
+    public function isImagick()
+    {
+        return is_a($this->data, 'Imagick');
+    }
+
+    /**
+     * Determines if current source data is Intervention\Image\Image object
+     *
+     * @return boolean
+     */
+    public function isInterventionImage()
+    {
+        return is_a($this->data, '\Intervention\Image\Image');
+    }
+
+    /**
+     * Determines if current data is SplFileInfo object
+     *
+     * @return boolean
+     */
+    public function isSplFileInfo()
+    {
+        return is_a($this->data, 'SplFileInfo');
+    }
+
+    /**
+     * Determines if current data is Symfony UploadedFile component
+     *
+     * @return boolean
+     */
+    public function isSymfonyUpload()
+    {
+        return is_a($this->data, 'Symfony\Component\HttpFoundation\File\UploadedFile');
+    }
+
+    /**
+     * Determines if current source data is file path
+     *
+     * @return boolean
+     */
+    public function isFilePath()
+    {
+        if (is_string($this->data)) {
+            return is_file($this->data);
+        }
+
+        return false;
+    }
+
+    /**
+     * Determines if current source data is url
+     *
+     * @return boolean
+     */
+    public function isUrl()
+    {
+        return (bool) filter_var($this->data, FILTER_VALIDATE_URL);
+    }
+
+    /**
+     * Determines if current source data is binary data
+     *
+     * @return boolean
+     */
+    public function isBinary()
+    {
+        if (is_string($this->data)) {
+            $mime = finfo_buffer(finfo_open(FILEINFO_MIME_TYPE), $this->data);
+            return (substr($mime, 0, 4) != 'text' && $mime != 'application/x-empty');
+        }
+
+        return false;
+    }
+
+    /**
+     * Determines if current source data is data-url
+     *
+     * @return boolean
+     */
+    public function isDataUrl()
+    {
+        $data = $this->decodeDataUrl($this->data);
+
+        return is_null($data) ? false : true;
+    }
+
+    /**
+     * Determines if current source data is base64 encoded
+     *
+     * @return boolean
+     */
+    public function isBase64()
+    {
+        return base64_encode(base64_decode($this->data)) === $this->data;
+    }
+
+    /**
+     * Initiates new Image from Intervention\Image\Image
+     *
+     * @param  Image $object
+     * @return \Intervention\Image\Image
+     */
+    public function initFromInterventionImage($object)
+    {
+        return $object;
+    }
+
+    /**
+     * Parses and decodes binary image data from data-url
+     *
+     * @param  string $data_url
+     * @return string
+     */
+    private function decodeDataUrl($data_url)
+    {
+        $pattern = "/^data:(?:image\/[a-zA-Z\-\.]+)(?:charset=\".+\")?;base64,(?P<data>.+)$/";
+        preg_match($pattern, $data_url, $matches);
+
+        if (is_array($matches) && array_key_exists('data', $matches)) {
+            return base64_decode($matches['data']);
+        }
+
+        return null;
+    }
+
+    /**
+     * Initiates new image from mixed data
+     *
+     * @param  mixed $data
+     * @return \Intervention\Image\Image
+     */
+    public function init($data)
+    {
+        $this->data = $data;
+
+        switch (true) {
+
+            case $this->isGdResource():
+                return $this->initFromGdResource($this->data);
+
+            case $this->isImagick():
+                return $this->initFromImagick($this->data);
+
+            case $this->isInterventionImage():
+                return $this->initFromInterventionImage($this->data);
+
+            case $this->isSplFileInfo():
+                return $this->initFromPath($this->data->getRealPath());
+
+            case $this->isBinary():
+                return $this->initFromBinary($this->data);
+
+            case $this->isUrl():
+                return $this->initFromUrl($this->data);
+
+            case $this->isFilePath():
+                return $this->initFromPath($this->data);
+
+            case $this->isDataUrl():
+                return $this->initFromBinary($this->decodeDataUrl($this->data));
+
+            case $this->isBase64():
+                return $this->initFromBinary(base64_decode($this->data));
+
+            default:
+                throw new Exception\NotReadableException("Image source not readable");
+        }
+    }
+
+    /**
+     * Decoder object transforms to string source data
+     *
+     * @return string
+     */
+    public function __toString()
+    {
+        return (string) $this->data;
+    }
+}

+ 132 - 0
plugins/intervention-image/Intervention/Image/AbstractDriver.php

@@ -0,0 +1,132 @@
+<?php
+
+namespace Intervention\Image;
+
+abstract class AbstractDriver
+{
+    /**
+     * Decoder instance to init images from
+     *
+     * @var \Intervention\Image\AbstractDecoder
+     */
+    public $decoder;
+
+    /**
+     * Image encoder instance
+     *
+     * @var \Intervention\Image\AbstractEncoder
+     */
+    public $encoder;
+
+    /**
+     * Creates new image instance
+     *
+     * @param  integer $width
+     * @param  integer $height
+     * @param  string  $background
+     * @return \Intervention\Image\Image
+     */
+    abstract public function newImage($width, $height, $background);
+
+    /**
+     * Reads given string into color object
+     *
+     * @param  string $value
+     * @return AbstractColor
+     */
+    abstract public function parseColor($value);
+
+    /**
+     * Checks if core module installation is available
+     *
+     * @return boolean
+     */
+    abstract protected function coreAvailable();
+
+    /**
+     * Returns clone of given core
+     *
+     * @return mixed
+     */
+    public function cloneCore($core)
+    {
+        return clone $core;
+    }
+
+    /**
+     * Initiates new image from given input
+     *
+     * @param  mixed $data
+     * @return \Intervention\Image\Image
+     */
+    public function init($data)
+    {
+        return $this->decoder->init($data);
+    }
+
+    /**
+     * Encodes given image
+     *
+     * @param  Image   $image
+     * @param  string  $format
+     * @param  integer $quality
+     * @return \Intervention\Image\Image
+     */
+    public function encode($image, $format, $quality)
+    {
+        return $this->encoder->process($image, $format, $quality);
+    }
+
+    /**
+     * Executes named command on given image
+     *
+     * @param  Image  $image
+     * @param  string $name
+     * @param  array $arguments
+     * @return \Intervention\Image\Commands\AbstractCommand
+     */
+    public function executeCommand($image, $name, $arguments)
+    {
+        $commandName = $this->getCommandClassName($name);
+        $command = new $commandName($arguments);
+        $command->execute($image);
+
+        return $command;
+    }
+
+    /**
+     * Returns classname of given command name
+     *
+     * @param  string $name
+     * @return string
+     */
+    private function getCommandClassName($name)
+    {
+        $drivername = $this->getDriverName();
+        $classnameLocal = sprintf('\Intervention\Image\%s\Commands\%sCommand', $drivername, ucfirst($name));
+        $classnameGlobal = sprintf('\Intervention\Image\Commands\%sCommand', ucfirst($name));
+
+        if (class_exists($classnameLocal)) {
+            return $classnameLocal;
+        } elseif (class_exists($classnameGlobal)) {
+            return $classnameGlobal;
+        }
+
+        throw new \Intervention\Image\Exception\NotSupportedException(
+            "Command ({$name}) is not available for driver ({$drivername})."
+        );
+    }
+
+    /**
+     * Returns name of current driver instance
+     *
+     * @return string
+     */
+    public function getDriverName()
+    {
+        $reflect = new \ReflectionClass($this);
+        $namespace = $reflect->getNamespaceName();
+
+        return substr(strrchr($namespace, "\\"), 1);
+    }
+}

+ 218 - 0
plugins/intervention-image/Intervention/Image/AbstractEncoder.php

@@ -0,0 +1,218 @@
+<?php
+
+namespace Intervention\Image;
+
+abstract class AbstractEncoder
+{
+    /**
+     * Buffer of encode result data
+     *
+     * @var string
+     */
+    public $result;
+
+    /**
+     * Image object to encode
+     *
+     * @var Image
+     */
+    public $image;
+
+    /**
+     * Output format of encoder instance
+     *
+     * @var string
+     */
+    public $format;
+
+    /**
+     * Output quality of encoder instance
+     *
+     * @var integer
+     */
+    public $quality;
+    
+    /**
+     * Processes and returns encoded image as JPEG string
+     *
+     * @return string
+     */
+    abstract protected function processJpeg();
+
+    /**
+     * Processes and returns encoded image as PNG string
+     *
+     * @return string
+     */
+    abstract protected function processPng();
+
+    /**
+     * Processes and returns encoded image as GIF string
+     *
+     * @return string
+     */
+    abstract protected function processGif();
+
+    /**
+     * Processes and returns encoded image as TIFF string
+     *
+     * @return string
+     */
+    abstract protected function processTiff();
+
+    /**
+     * Processes and returns encoded image as BMP string
+     *
+     * @return string
+     */
+    abstract protected function processBmp();
+
+    /**
+     * Processes and returns encoded image as ICO string
+     *
+     * @return string
+     */
+    abstract protected function processIco();
+
+    /**
+     * Process a given image
+     *
+     * @param  Image   $image
+     * @param  string  $format
+     * @param  integer $quality
+     * @return Image
+     */
+    public function process(Image $image, $format = null, $quality = null)
+    {
+        $this->setImage($image);
+        $this->setFormat($format);
+        $this->setQuality($quality);
+
+        switch (strtolower($this->format)) {
+
+            case 'data-url':
+                $this->result = $this->processDataUrl();
+                break;
+
+            case 'gif':
+            case 'image/gif':
+                $this->result = $this->processGif();
+                break;
+
+            case 'png':
+            case 'image/png':
+            case 'image/x-png':
+                $this->result = $this->processPng();
+                break;
+
+            case 'jpg':
+            case 'jpeg':
+            case 'image/jpg':
+            case 'image/jpeg':
+            case 'image/pjpeg':
+                $this->result = $this->processJpeg();
+                break;
+
+            case 'tif':
+            case 'tiff':
+            case 'image/tiff':
+            case 'image/tif':
+            case 'image/x-tif':
+            case 'image/x-tiff':
+                $this->result = $this->processTiff();
+                break;
+
+            case 'bmp':
+            case 'image/bmp':
+            case 'image/ms-bmp':
+            case 'image/x-bitmap':
+            case 'image/x-bmp':
+            case 'image/x-ms-bmp':
+            case 'image/x-win-bitmap':
+            case 'image/x-windows-bmp':
+            case 'image/x-xbitmap':
+                $this->result = $this->processBmp();
+                break;
+
+            case 'ico':
+            case 'image/x-icon':
+            case 'image/vnd.microsoft.icon':
+                $this->result = $this->processIco();
+                break;
+
+            case 'psd':
+            case 'image/vnd.adobe.photoshop':
+                $this->result = $this->processPsd();
+                break;
+                
+            default:
+                throw new \Intervention\Image\Exception\NotSupportedException(
+                    "Encoding format ({$format}) is not supported."
+                );
+        }
+
+        return $image->setEncoded($this->result);
+    }
+
+    /**
+     * Processes and returns encoded image as data-url string
+     *
+     * @return string
+     */
+    protected function processDataUrl()
+    {
+        $mime = $this->image->mime ? $this->image->mime : 'image/png';
+
+        return sprintf('data:%s;base64,%s',
+            $mime,
+            base64_encode($this->process($this->image, $mime, $this->quality))
+        );
+    }
+
+    /**
+     * Sets image to process
+     *
+     * @param Image $image
+     */
+    protected function setImage($image)
+    {
+        $this->image = $image;
+    }
+
+    /**
+     * Determines output format
+     *
+     * @param string $format
+     */
+    protected function setFormat($format = null)
+    {
+        if ($format == '' && $this->image instanceof Image) {
+            $format = $this->image->mime;
+        }
+
+        $this->format = $format ? $format : 'jpg';
+
+        return $this;
+    }
+
+    /**
+     * Determines output quality
+     *
+     * @param integer $quality
+     */
+    protected function setQuality($quality)
+    {
+        $quality = is_null($quality) ? 90 : $quality;
+        $quality = $quality === 0 ? 1 : $quality;
+
+        if ($quality < 0 || $quality > 100) {
+            throw new \Intervention\Image\Exception\InvalidArgumentException(
+                'Quality must range from 0 to 100.'
+            );
+        }
+
+        $this->quality = intval($quality);
+
+        return $this;
+    }
+}

+ 246 - 0
plugins/intervention-image/Intervention/Image/AbstractFont.php

@@ -0,0 +1,246 @@
+<?php
+
+namespace Intervention\Image;
+
+abstract class AbstractFont
+{
+    /**
+     * Text to be written
+     *
+     * @var String
+     */
+    public $text;
+
+    /**
+     * Text size in pixels
+     *
+     * @var integer
+     */
+    public $size = 12;
+
+    /**
+     * Color of the text
+     *
+     * @var mixed
+     */
+    public $color = '000000';
+
+    /**
+     * Rotation angle of the text
+     *
+     * @var integer
+     */
+    public $angle = 0;
+
+    /**
+     * Horizontal alignment of the text
+     *
+     * @var String
+     */
+    public $align;
+
+    /**
+     * Vertical alignment of the text
+     *
+     * @var String
+     */
+    public $valign;
+
+    /**
+     * Path to TTF or GD library internal font file of the text
+     *
+     * @var mixed
+     */
+    public $file;
+
+    /**
+     * Draws font to given image on given position
+     *
+     * @param  Image   $image
+     * @param  integer $posx
+     * @param  integer $posy
+     * @return boolean
+     */
+    abstract public function applyToImage(Image $image, $posx = 0, $posy = 0);
+
+    /**
+     * Create a new instance of Font
+     *
+     * @param Strinf $text Text to be written
+     */
+    public function __construct($text = null)
+    {
+        $this->text = $text;
+    }
+
+    /**
+     * Set text to be written
+     *
+     * @param  String $text
+     * @return void
+     */
+    public function text($text)
+    {
+        $this->text = $text;
+    }
+
+    /**
+     * Get text to be written
+     *
+     * @return String
+     */
+    public function getText()
+    {
+        return $this->text;
+    }
+
+    /**
+     * Set font size in pixels
+     *
+     * @param  integer $size
+     * @return void
+     */
+    public function size($size)
+    {
+        $this->size = $size;
+    }
+
+    /**
+     * Get font size in pixels
+     *
+     * @return integer
+     */
+    public function getSize()
+    {
+        return $this->size;
+    }
+
+    /**
+     * Set color of text to be written
+     *
+     * @param  mixed $color
+     * @return void
+     */
+    public function color($color)
+    {
+        $this->color = $color;
+    }
+
+    /**
+     * Get color of text
+     *
+     * @return mixed
+     */
+    public function getColor()
+    {
+        return $this->color;
+    }
+
+    /**
+     * Set rotation angle of text
+     *
+     * @param  integer $angle
+     * @return void
+     */
+    public function angle($angle)
+    {
+        $this->angle = $angle;
+    }
+
+    /**
+     * Get rotation angle of text
+     *
+     * @return integer
+     */
+    public function getAngle()
+    {
+        return $this->angle;
+    }
+
+    /**
+     * Set horizontal text alignment
+     *
+     * @param  string $align
+     * @return void
+     */
+    public function align($align)
+    {
+        $this->align = $align;
+    }
+
+    /**
+     * Get horizontal text alignment
+     *
+     * @return string
+     */
+    public function getAlign()
+    {
+        return $this->align;
+    }
+
+    /**
+     * Set vertical text alignment
+     *
+     * @param  string $valign
+     * @return void
+     */
+    public function valign($valign)
+    {
+        $this->valign = $valign;
+    }
+
+    /**
+     * Get vertical text alignment
+     *
+     * @return string
+     */
+    public function getValign()
+    {
+        return $this->valign;
+    }
+
+    /**
+     * Set path to font file
+     *
+     * @param  string $align
+     * @return void
+     */
+    public function file($file)
+    {
+        $this->file = $file;
+    }
+
+    /**
+     * Get path to font file
+     *
+     * @return string
+     */
+    public function getFile()
+    {
+        return $this->file;
+    }
+
+    /**
+     * Checks if current font has access to an applicable font file
+     *
+     * @return boolean
+     */
+    protected function hasApplicableFontFile()
+    {
+        if (is_string($this->file)) {
+            return file_exists($this->file);
+        }
+
+        return false;
+    }
+
+    /**
+     * Counts lines of text to be written
+     *
+     * @return integer
+     */
+    public function countLines()
+    {
+        return count(explode(PHP_EOL, $this->text));
+    }
+}

+ 71 - 0
plugins/intervention-image/Intervention/Image/AbstractShape.php

@@ -0,0 +1,71 @@
+<?php
+
+namespace Intervention\Image;
+
+abstract class AbstractShape
+{
+    /**
+     * Background color of shape
+     *
+     * @var string
+     */
+    public $background;
+
+    /**
+     * Border color of current shape
+     *
+     * @var string
+     */
+    public $border_color;
+
+    /**
+     * Border width of shape
+     *
+     * @var integer
+     */
+    public $border_width = 0;
+
+    /**
+     * Draws shape to given image on given position
+     *
+     * @param  Image   $image
+     * @param  integer $posx
+     * @param  integer $posy
+     * @return boolean
+     */
+    abstract public function applyToImage(Image $image, $posx = 0, $posy = 0);
+
+    /**
+     * Set text to be written
+     *
+     * @param  string $text
+     * @return void
+     */
+    public function background($color)
+    {
+        $this->background = $color;
+    }
+
+    /**
+     * Set border width and color of current shape
+     *
+     * @param  integer $width
+     * @param  string  $color
+     * @return void
+     */
+    public function border($width, $color = null)
+    {
+        $this->border_width = is_numeric($width) ? intval($width) : 0;
+        $this->border_color = is_null($color) ? '#000000' : $color;
+    }
+
+    /**
+     * Determines if current shape has border
+     *
+     * @return boolean
+     */
+    public function hasBorder()
+    {
+        return ($this->border_width >= 1);
+    }
+}

+ 79 - 0
plugins/intervention-image/Intervention/Image/Commands/AbstractCommand.php

@@ -0,0 +1,79 @@
+<?php
+
+namespace Intervention\Image\Commands;
+
+abstract class AbstractCommand
+{
+    /**
+     * Arguments of command
+     *
+     * @var array
+     */
+    public $arguments;
+
+    /**
+     * Output of command
+     *
+     * @var mixed
+     */
+    protected $output;
+
+    /**
+     * Executes current command on given image
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return mixed
+     */
+    abstract public function execute($image);
+
+    /**
+     * Creates new command instance
+     *
+     * @param array $arguments
+     */
+    public function __construct($arguments)
+    {
+        $this->arguments = $arguments;
+    }
+
+    /**
+     * Creates new argument instance from given argument key
+     *
+     * @param  integer $key
+     * @return \Intervention\Image\Commands\Argument
+     */
+    public function argument($key)
+    {
+        return new \Intervention\Image\Commands\Argument($this, $key);
+    }
+
+    /**
+     * Returns output data of current command
+     *
+     * @return mixed
+     */
+    public function getOutput()
+    {
+        return $this->output ? $this->output : null;
+    }
+
+    /**
+     * Determines if current instance has output data
+     *
+     * @return boolean
+     */
+    public function hasOutput()
+    {
+        return ! is_null($this->output);
+    }
+
+    /**
+     * Sets output data of current command
+     *
+     * @param mixed $value
+     */
+    public function setOutput($value)
+    {
+        $this->output = $value;
+    }
+}

+ 225 - 0
plugins/intervention-image/Intervention/Image/Commands/Argument.php

@@ -0,0 +1,225 @@
+<?php
+
+namespace Intervention\Image\Commands;
+
+class Argument
+{
+    /**
+     * Command with arguments
+     *
+     * @var AbstractCommand
+     */
+    public $command;
+
+    /**
+     * Key of argument in array
+     *
+     * @var integer
+     */
+    public $key;
+
+    /**
+     * Creates new instance from given command and key
+     *
+     * @param AbstractCommand $command
+     * @param integer         $key
+     */
+    public function __construct(AbstractCommand $command, $key = 0)
+    {
+        $this->command = $command;
+        $this->key = $key;
+    }
+
+    /**
+     * Returns name of current arguments command
+     *
+     * @return string
+     */
+    public function getCommandName()
+    {
+        preg_match("/\\\\([\w]+)Command$/", get_class($this->command), $matches);
+        return isset($matches[1]) ? lcfirst($matches[1]).'()' : 'Method';
+    }
+
+    /**
+     * Returns value of current argument
+     *
+     * @param  mixed $default
+     * @return mixed
+     */
+    public function value($default = null)
+    {
+        $arguments = $this->command->arguments;
+
+        if (is_array($arguments)) {
+            return isset($arguments[$this->key]) ? $arguments[$this->key] : $default;
+        }
+
+        return $default;
+    }
+
+    /**
+     * Defines current argument as required
+     *
+     * @return \Intervention\Image\Commands\Argument
+     */
+    public function required()
+    {
+        if ( ! array_key_exists($this->key, $this->command->arguments)) {
+            throw new \Intervention\Image\Exception\InvalidArgumentException(
+                sprintf("Missing argument %d for %s", $this->key + 1, $this->getCommandName())
+            );
+        }
+
+        return $this;
+    }
+
+    /**
+     * Determines that current argument must be of given type
+     *
+     * @return \Intervention\Image\Commands\Argument
+     */
+    public function type($type)
+    {
+        $fail = false;
+
+        $value = $this->value();
+
+        if (is_null($value)) {
+            return $this;
+        }
+
+        switch (strtolower($type)) {
+
+            case 'bool':
+            case 'boolean':
+                $fail =  ! is_bool($value);
+                $message = sprintf('%s accepts only boolean values as argument %d.', $this->getCommandName(), $this->key + 1);
+                break;
+
+            case 'int':
+            case 'integer':
+                $fail =  ! is_integer($value);
+                $message = sprintf('%s accepts only integer values as argument %d.', $this->getCommandName(), $this->key + 1);
+                break;
+
+            case 'num':
+            case 'numeric':
+                $fail =  ! is_numeric($value);
+                $message = sprintf('%s accepts only numeric values as argument %d.', $this->getCommandName(), $this->key + 1);
+                break;
+
+            case 'str':
+            case 'string':
+                $fail =  ! is_string($value);
+                $message = sprintf('%s accepts only string values as argument %d.', $this->getCommandName(), $this->key + 1);
+                break;
+
+            case 'array':
+                $fail =  ! is_array($value);
+                $message = sprintf('%s accepts only array as argument %d.', $this->getCommandName(), $this->key + 1);
+                break;
+
+            case 'closure':
+                $fail =  ! is_a($value, '\Closure');
+                $message = sprintf('%s accepts only Closure as argument %d.', $this->getCommandName(), $this->key + 1);
+                break;
+
+            case 'digit':
+                $fail = ! $this->isDigit($value);
+                $message = sprintf('%s accepts only integer values as argument %d.', $this->getCommandName(), $this->key + 1);
+                break;
+        }
+
+        if ($fail) {
+
+            $message = isset($message) ? $message : sprintf("Missing argument for %d.", $this->key);
+
+            throw new \Intervention\Image\Exception\InvalidArgumentException(
+                $message
+            );
+        }
+
+        return $this;
+    }
+
+    /**
+     * Determines that current argument value must be numeric between given values
+     *
+     * @return \Intervention\Image\Commands\Argument
+     */
+    public function between($x, $y)
+    {
+        $value = $this->type('numeric')->value();
+
+        if (is_null($value)) {
+            return $this;
+        }
+
+        $alpha = min($x, $y);
+        $omega = max($x, $y);
+
+        if ($value < $alpha || $value > $omega) {
+            throw new \Intervention\Image\Exception\InvalidArgumentException(
+                sprintf('Argument %d must be between %s and %s.', $this->key, $x, $y)
+            );
+        }
+
+        return $this;
+    }
+
+    /**
+     * Determines that current argument must be over a minimum value
+     *
+     * @return \Intervention\Image\Commands\Argument
+     */
+    public function min($value)
+    {
+        $v = $this->type('numeric')->value();
+
+        if (is_null($v)) {
+            return $this;
+        }
+
+        if ($v < $value) {
+            throw new \Intervention\Image\Exception\InvalidArgumentException(
+                sprintf('Argument %d must be at least %s.', $this->key, $value)
+            );
+        }
+
+        return $this;
+    }
+
+    /**
+     * Determines that current argument must be under a maxiumum value
+     *
+     * @return \Intervention\Image\Commands\Argument
+     */
+    public function max($value)
+    {
+        $v = $this->type('numeric')->value();
+
+        if (is_null($v)) {
+            return $this;
+        }
+
+        if ($v > $value) {
+            throw new \Intervention\Image\Exception\InvalidArgumentException(
+                sprintf('Argument %d may not be greater than %s.', $this->key, $value)
+            );
+        }
+
+        return $this;
+    }
+
+    /**
+     * Checks if value is "PHP" integer (120 but also 120.0)
+     *
+     * @param  mixed $value
+     * @return boolean
+     */
+    private function isDigit($value)
+    {
+        return is_numeric($value) ? intval($value) == $value : false;
+    }
+}

+ 29 - 0
plugins/intervention-image/Intervention/Image/Commands/ChecksumCommand.php

@@ -0,0 +1,29 @@
+<?php
+
+namespace Intervention\Image\Commands;
+
+class ChecksumCommand extends AbstractCommand
+{
+    /**
+     * Calculates checksum of given image
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $colors = array();
+
+        $size = $image->getSize();
+
+        for ($x=0; $x <= ($size->width-1); $x++) {
+            for ($y=0; $y <= ($size->height-1); $y++) {
+                $colors[] = $image->pickColor($x, $y, 'array');
+            }
+        }
+
+        $this->setOutput(md5(serialize($colors)));
+
+        return true;
+    }
+}

+ 35 - 0
plugins/intervention-image/Intervention/Image/Commands/CircleCommand.php

@@ -0,0 +1,35 @@
+<?php
+
+namespace Intervention\Image\Commands;
+
+use \Closure;
+
+class CircleCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Draw a circle centered on given image
+     *
+     * @param  \Intervention\Image\image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $diameter = $this->argument(0)->type('numeric')->required()->value();
+        $x = $this->argument(1)->type('numeric')->required()->value();
+        $y = $this->argument(2)->type('numeric')->required()->value();
+        $callback = $this->argument(3)->type('closure')->value();
+
+        $circle_classname = sprintf('\Intervention\Image\%s\Shapes\CircleShape',
+            $image->getDriver()->getDriverName());
+
+        $circle = new $circle_classname($diameter);
+
+        if ($callback instanceof Closure) {
+            $callback($circle);
+        }
+
+        $circle->applyToImage($image, $x, $y);
+
+        return true;
+    }
+}

+ 36 - 0
plugins/intervention-image/Intervention/Image/Commands/EllipseCommand.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace Intervention\Image\Commands;
+
+use \Closure;
+
+class EllipseCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Draws ellipse on given image
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $width = $this->argument(0)->type('numeric')->required()->value();
+        $height = $this->argument(1)->type('numeric')->required()->value();
+        $x = $this->argument(2)->type('numeric')->required()->value();
+        $y = $this->argument(3)->type('numeric')->required()->value();
+        $callback = $this->argument(4)->type('closure')->value();
+
+        $ellipse_classname = sprintf('\Intervention\Image\%s\Shapes\EllipseShape',
+            $image->getDriver()->getDriverName());
+
+        $ellipse = new $ellipse_classname($width, $height);
+
+        if ($callback instanceof Closure) {
+            $callback($ellipse);
+        }
+
+        $ellipse->applyToImage($image, $x, $y);
+
+        return true;
+    }
+}

+ 37 - 0
plugins/intervention-image/Intervention/Image/Commands/ExifCommand.php

@@ -0,0 +1,37 @@
+<?php
+
+namespace Intervention\Image\Commands;
+
+class ExifCommand extends AbstractCommand
+{
+    /**
+     * Read Exif data from the given image
+     *
+     * Note: Windows PHP Users - in order to use this method you will need to
+     * enable the mbstring and exif extensions within the php.ini file.
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        if ( ! function_exists('exif_read_data')) {
+            throw new \Intervention\Image\Exception\NotSupportedException(
+                "Reading Exif data is not supported by this PHP installation."
+            );
+        }
+
+        $key = $this->argument(0)->value();
+
+        // try to read exif data from image file
+        $data = @exif_read_data($image->dirname .'/'. $image->basename);
+
+        if (! is_null($key) && is_array($data)) {
+            $data = array_key_exists($key, $data) ? $data[$key] : false;
+        }
+
+        $this->setOutput($data);
+
+        return true;
+    }
+}

+ 62 - 0
plugins/intervention-image/Intervention/Image/Commands/IptcCommand.php

@@ -0,0 +1,62 @@
+<?php
+
+namespace Intervention\Image\Commands;
+
+class IptcCommand extends AbstractCommand
+{
+    /**
+     * Read Iptc data from the given image
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        if ( ! function_exists('iptcparse')) {
+            throw new \Intervention\Image\Exception\NotSupportedException(
+                "Reading Iptc data is not supported by this PHP installation."
+            );
+        }
+
+        $key = $this->argument(0)->value();
+
+        $info = array();
+        @getimagesize($image->dirname .'/'. $image->basename, $info);
+
+        $data = array();
+
+        if (array_key_exists('APP13', $info)) {
+            $iptc = iptcparse($info['APP13']);
+
+            if (is_array($iptc)) {
+                $data['DocumentTitle'] = isset($iptc["2#005"][0]) ? $iptc["2#005"][0] : null;
+                $data['Urgency'] = isset($iptc["2#010"][0]) ? $iptc["2#010"][0] : null;
+                $data['Category'] = isset($iptc["2#015"][0]) ? $iptc["2#015"][0] : null;
+                $data['Subcategories'] = isset($iptc["2#020"][0]) ? $iptc["2#020"][0] : null;
+                $data['Keywords'] = isset($iptc["2#025"][0]) ? $iptc["2#025"][0] : null;
+                $data['SpecialInstructions'] = isset($iptc["2#040"][0]) ? $iptc["2#040"][0] : null;
+                $data['CreationDate'] = isset($iptc["2#055"][0]) ? $iptc["2#055"][0] : null;
+                $data['AuthorByline'] = isset($iptc["2#080"][0]) ? $iptc["2#080"][0] : null;
+                $data['AuthorTitle'] = isset($iptc["2#085"][0]) ? $iptc["2#085"][0] : null;
+                $data['City'] = isset($iptc["2#090"][0]) ? $iptc["2#090"][0] : null;
+                $data['State'] = isset($iptc["2#095"][0]) ? $iptc["2#095"][0] : null;
+                $data['Country'] = isset($iptc["2#101"][0]) ? $iptc["2#101"][0] : null;
+                $data['OTR'] = isset($iptc["2#103"][0]) ? $iptc["2#103"][0] : null;
+                $data['Headline'] = isset($iptc["2#105"][0]) ? $iptc["2#105"][0] : null;
+                $data['Source'] = isset($iptc["2#110"][0]) ? $iptc["2#110"][0] : null;
+                $data['PhotoSource'] = isset($iptc["2#115"][0]) ? $iptc["2#115"][0] : null;
+                $data['Copyright'] = isset($iptc["2#116"][0]) ? $iptc["2#116"][0] : null;
+                $data['Caption'] = isset($iptc["2#120"][0]) ? $iptc["2#120"][0] : null;
+                $data['CaptionWriter'] = isset($iptc["2#120"][0]) ? $iptc["2#122"][0] : null;
+            }
+        }
+
+        if (! is_null($key) && is_array($data)) {
+            $data = array_key_exists($key, $data) ? $data[$key] : false;
+        }
+
+        $this->setOutput($data);
+
+        return true;
+    }
+}

+ 36 - 0
plugins/intervention-image/Intervention/Image/Commands/LineCommand.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace Intervention\Image\Commands;
+
+use \Closure;
+
+class LineCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Draws line on given image
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $x1 = $this->argument(0)->type('numeric')->required()->value();
+        $y1 = $this->argument(1)->type('numeric')->required()->value();
+        $x2 = $this->argument(2)->type('numeric')->required()->value();
+        $y2 = $this->argument(3)->type('numeric')->required()->value();
+        $callback = $this->argument(4)->type('closure')->value();
+
+        $line_classname = sprintf('\Intervention\Image\%s\Shapes\LineShape',
+            $image->getDriver()->getDriverName());
+
+        $line = new $line_classname($x2, $y2);
+
+        if ($callback instanceof Closure) {
+            $callback($line);
+        }
+
+        $line->applyToImage($image, $x1, $y1);
+
+        return true;
+    }
+}

+ 48 - 0
plugins/intervention-image/Intervention/Image/Commands/OrientateCommand.php

@@ -0,0 +1,48 @@
+<?php
+
+namespace Intervention\Image\Commands;
+
+class OrientateCommand extends AbstractCommand
+{
+    /**
+     * Correct image orientation according to Exif data
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        switch ($image->exif('Orientation')) {
+
+            case 2:
+                $image->flip();
+                break;
+
+            case 3:
+                $image->rotate(180);
+                break;
+
+            case 4:
+                $image->rotate(180)->flip();
+                break;
+
+            case 5:
+                $image->rotate(270)->flip();
+                break;
+
+            case 6:
+                $image->rotate(270);
+                break;
+
+            case 7:
+                $image->rotate(90)->flip();
+                break;
+
+            case 8:
+                $image->rotate(90);
+                break;
+        }
+
+        return true;
+    }
+}

+ 48 - 0
plugins/intervention-image/Intervention/Image/Commands/PolygonCommand.php

@@ -0,0 +1,48 @@
+<?php
+
+namespace Intervention\Image\Commands;
+
+use \Closure;
+
+class PolygonCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Draw a polygon on given image
+     *
+     * @param  \Intervention\Image\image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $points = $this->argument(0)->type('array')->required()->value();
+        $callback = $this->argument(1)->type('closure')->value();
+
+        $vertices_count = count($points);
+
+        // check if number if coordinates is even
+        if ($vertices_count % 2 !== 0) {
+            throw new \Intervention\Image\Exception\InvalidArgumentException(
+                "The number of given polygon vertices must be even."
+            );
+        }
+
+        if ($vertices_count < 6) {
+            throw new \Intervention\Image\Exception\InvalidArgumentException(
+                "You must have at least 3 points in your array."
+            );
+        }
+        
+        $polygon_classname = sprintf('\Intervention\Image\%s\Shapes\PolygonShape',
+            $image->getDriver()->getDriverName());
+
+        $polygon = new $polygon_classname($points);
+        
+        if ($callback instanceof Closure) {
+            $callback($polygon);
+        }
+
+        $polygon->applyToImage($image);
+
+        return true;
+    }
+}

+ 36 - 0
plugins/intervention-image/Intervention/Image/Commands/RectangleCommand.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace Intervention\Image\Commands;
+
+use \Closure;
+
+class RectangleCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Draws rectangle on given image
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $x1 = $this->argument(0)->type('numeric')->required()->value();
+        $y1 = $this->argument(1)->type('numeric')->required()->value();
+        $x2 = $this->argument(2)->type('numeric')->required()->value();
+        $y2 = $this->argument(3)->type('numeric')->required()->value();
+        $callback = $this->argument(4)->type('closure')->value();
+
+        $rectangle_classname = sprintf('\Intervention\Image\%s\Shapes\RectangleShape',
+            $image->getDriver()->getDriverName());
+
+        $rectangle = new $rectangle_classname($x1, $y1, $x2, $y2);
+
+        if ($callback instanceof Closure) {
+            $callback($rectangle);
+        }
+
+        $rectangle->applyToImage($image, $x1, $y1);
+
+        return true;
+    }
+}

+ 26 - 0
plugins/intervention-image/Intervention/Image/Commands/ResponseCommand.php

@@ -0,0 +1,26 @@
+<?php
+
+namespace Intervention\Image\Commands;
+
+use \Intervention\Image\Response;
+
+class ResponseCommand extends AbstractCommand
+{
+    /**
+     * Builds HTTP response from given image
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $format = $this->argument(0)->value();
+        $quality = $this->argument(1)->between(0, 100)->value();
+
+        $response = new Response($image, $format, $quality);
+
+        $this->setOutput($response->make());
+
+        return true;
+    }
+}

+ 34 - 0
plugins/intervention-image/Intervention/Image/Commands/TextCommand.php

@@ -0,0 +1,34 @@
+<?php
+
+namespace Intervention\Image\Commands;
+
+use \Closure;
+
+class TextCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Write text on given image
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $text = $this->argument(0)->required()->value();
+        $x = $this->argument(1)->type('numeric')->value(0);
+        $y = $this->argument(2)->type('numeric')->value(0);
+        $callback = $this->argument(3)->type('closure')->value();
+
+        $fontclassname = sprintf('\Intervention\Image\%s\Font',
+            $image->getDriver()->getDriverName());
+
+        $font = new $fontclassname($text);
+
+        if ($callback instanceof Closure) {
+            $callback($font);
+        }
+
+        $font->applyToImage($image, $x, $y);
+
+        return true;
+    }
+}

+ 91 - 0
plugins/intervention-image/Intervention/Image/Constraint.php

@@ -0,0 +1,91 @@
+<?php
+
+namespace Intervention\Image;
+
+class Constraint
+{
+    /**
+     * Bit value of aspect ratio constraint
+     */
+    const ASPECTRATIO = 1;
+
+    /**
+     * Bit value of upsize constraint
+     */
+    const UPSIZE = 2;
+
+    /**
+     * Constraint size
+     *
+     * @var \Intervention\Image\Size
+     */
+    private $size;
+
+    /**
+     * Integer value of fixed parameters
+     *
+     * @var integer
+     */
+    private $fixed = 0;
+
+    /**
+     * Create a new constraint based on size
+     *
+     * @param Size $size
+     */
+    public function __construct(Size $size)
+    {
+        $this->size = $size;
+    }
+
+    /**
+     * Returns current size of constraint
+     *
+     * @return \Intervention\Image\Size
+     */
+    public function getSize()
+    {
+        return $this->size;
+    }
+
+    /**
+     * Fix the given argument in current constraint
+     * @param  integer $type
+     * @return void
+     */
+    public function fix($type)
+    {
+        $this->fixed = ($this->fixed & ~(1 << $type)) | (1 << $type);
+    }
+
+    /**
+     * Checks if given argument is fixed in current constraint
+     *
+     * @param  integer  $type
+     * @return boolean
+     */
+    public function isFixed($type)
+    {
+        return (bool) ($this->fixed & (1 << $type));
+    }
+
+    /**
+     * Fixes aspect ratio in current constraint
+     *
+     * @return void
+     */
+    public function aspectRatio()
+    {
+        $this->fix(self::ASPECTRATIO);
+    }
+
+    /**
+     * Fixes possibility to size up in current constraint
+     *
+     * @return void
+     */
+    public function upsize()
+    {
+        $this->fix(self::UPSIZE);
+    }
+}

+ 8 - 0
plugins/intervention-image/Intervention/Image/Exception/InvalidArgumentException.php

@@ -0,0 +1,8 @@
+<?php
+
+namespace Intervention\Image\Exception;
+
+class InvalidArgumentException extends \RuntimeException
+{
+    # nothing to override
+}

+ 8 - 0
plugins/intervention-image/Intervention/Image/Exception/MissingDependencyException.php

@@ -0,0 +1,8 @@
+<?php
+
+namespace Intervention\Image\Exception;
+
+class MissingDependencyException extends \RuntimeException
+{
+    # nothing to override
+}

+ 8 - 0
plugins/intervention-image/Intervention/Image/Exception/NotFoundException.php

@@ -0,0 +1,8 @@
+<?php
+
+namespace Intervention\Image\Exception;
+
+class NotFoundException extends \RuntimeException
+{
+    # nothing to override
+}

+ 8 - 0
plugins/intervention-image/Intervention/Image/Exception/NotReadableException.php

@@ -0,0 +1,8 @@
+<?php
+
+namespace Intervention\Image\Exception;
+
+class NotReadableException extends \RuntimeException
+{
+    # nothing to override
+}

+ 8 - 0
plugins/intervention-image/Intervention/Image/Exception/NotSupportedException.php

@@ -0,0 +1,8 @@
+<?php
+
+namespace Intervention\Image\Exception;
+
+class NotSupportedException extends \RuntimeException
+{
+    # nothing to override
+}

+ 8 - 0
plugins/intervention-image/Intervention/Image/Exception/NotWritableException.php

@@ -0,0 +1,8 @@
+<?php
+
+namespace Intervention\Image\Exception;
+
+class NotWritableException extends \RuntimeException
+{
+    # nothing to override
+}

+ 8 - 0
plugins/intervention-image/Intervention/Image/Exception/RuntimeException.php

@@ -0,0 +1,8 @@
+<?php
+
+namespace Intervention\Image\Exception;
+
+class RuntimeException extends \RuntimeException
+{
+    # nothing to override
+}

+ 13 - 0
plugins/intervention-image/Intervention/Image/Facades/Image.php

@@ -0,0 +1,13 @@
+<?php
+
+namespace Intervention\Image\Facades;
+
+use Illuminate\Support\Facades\Facade;
+
+class Image extends Facade
+{
+    protected static function getFacadeAccessor()
+    {
+        return 'image';
+    }
+}

+ 78 - 0
plugins/intervention-image/Intervention/Image/File.php

@@ -0,0 +1,78 @@
+<?php
+
+namespace Intervention\Image;
+
+class File
+{
+    /**
+     * Mime type
+     *
+     * @var string
+     */
+    public $mime;
+
+    /**
+     * Name of directory path
+     *
+     * @var string
+     */
+    public $dirname;
+
+    /**
+     * Basename of current file
+     *
+     * @var string
+     */
+    public $basename;
+
+    /**
+     * File extension of current file
+     *
+     * @var string
+     */
+    public $extension;
+
+    /**
+     * File name of current file
+     *
+     * @var string
+     */
+    public $filename;
+
+    /**
+     * Sets all instance properties from given path
+     *
+     * @param string $path
+     */
+    public function setFileInfoFromPath($path)
+    {
+        $info = pathinfo($path);
+        $this->dirname = array_key_exists('dirname', $info) ? $info['dirname'] : null;
+        $this->basename = array_key_exists('basename', $info) ? $info['basename'] : null;
+        $this->extension = array_key_exists('extension', $info) ? $info['extension'] : null;
+        $this->filename = array_key_exists('filename', $info) ? $info['filename'] : null;
+
+        if (file_exists($path) && is_file($path)) {
+            $this->mime = finfo_file(finfo_open(FILEINFO_MIME_TYPE), $path);
+        }
+
+        return $this;
+    }
+
+     /**
+      * Get file size
+      * 
+      * @return mixed
+      */
+    public function filesize()
+    {
+        $path = $this->basePath();
+
+        if (file_exists($path) && is_file($path)) {
+            return filesize($path);
+        }
+        
+        return false;
+    }
+
+}

+ 42 - 0
plugins/intervention-image/Intervention/Image/Filters/DemoFilter.php

@@ -0,0 +1,42 @@
+<?php
+
+namespace Intervention\Image\Filters;
+
+class DemoFilter implements FilterInterface
+{
+    /**
+     * Default size of filter effects
+     */
+    const DEFAULT_SIZE = 10;
+
+    /**
+     * Size of filter effects
+     *
+     * @var integer
+     */
+    private $size;
+
+    /**
+     * Creates new instance of filter
+     *
+     * @param integer $size
+     */
+    public function __construct($size = null)
+    {
+        $this->size = is_numeric($size) ? intval($size) : self::DEFAULT_SIZE;
+    }
+
+    /**
+     * Applies filter effects to given image
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return \Intervention\Image\Image
+     */
+    public function applyFilter(\Intervention\Image\Image $image)
+    {
+        $image->pixelate($this->size);
+        $image->greyscale();
+
+        return $image;
+    }
+}

+ 14 - 0
plugins/intervention-image/Intervention/Image/Filters/FilterInterface.php

@@ -0,0 +1,14 @@
+<?php
+
+namespace Intervention\Image\Filters;
+
+interface FilterInterface
+{
+    /**
+     * Applies filter to given image
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return \Intervention\Image\Image
+     */
+    public function applyFilter(\Intervention\Image\Image $image);
+}

+ 226 - 0
plugins/intervention-image/Intervention/Image/Gd/Color.php

@@ -0,0 +1,226 @@
+<?php
+
+namespace Intervention\Image\Gd;
+
+use Intervention\Image\AbstractColor;
+
+class Color extends AbstractColor
+{
+    /**
+     * RGB Red value of current color instance
+     *
+     * @var integer
+     */
+    public $r;
+
+    /**
+     * RGB Green value of current color instance
+     *
+     * @var integer
+     */
+    public $g;
+
+    /**
+     * RGB Blue value of current color instance
+     *
+     * @var integer
+     */
+    public $b;
+
+    /**
+     * RGB Alpha value of current color instance
+     *
+     * @var float
+     */
+    public $a;
+
+    /**
+     * Initiates color object from integer
+     *
+     * @param  integer $value
+     * @return \Intervention\Image\AbstractColor
+     */
+    public function initFromInteger($value)
+    {
+        $this->a = ($value >> 24) & 0xFF;
+        $this->r = ($value >> 16) & 0xFF;
+        $this->g = ($value >> 8) & 0xFF;
+        $this->b = $value & 0xFF;
+    }
+
+    /**
+     * Initiates color object from given array
+     *
+     * @param  array $value
+     * @return \Intervention\Image\AbstractColor
+     */
+    public function initFromArray($array)
+    {
+        $array = array_values($array);
+
+        if (count($array) == 4) {
+
+            // color array with alpha value
+            list($r, $g, $b, $a) = $array;
+            $this->a = $this->alpha2gd($a);
+
+        } elseif (count($array) == 3) {
+
+            // color array without alpha value
+            list($r, $g, $b) = $array;
+            $this->a = 0;
+
+        }
+
+        $this->r = $r;
+        $this->g = $g;
+        $this->b = $b;
+    }
+
+    /**
+     * Initiates color object from given string
+     *
+     * @param  string $value
+     * @return \Intervention\Image\AbstractColor
+     */
+    public function initFromString($value)
+    {
+        if ($color = $this->rgbaFromString($value)) {
+            $this->r = $color[0];
+            $this->g = $color[1];
+            $this->b = $color[2];
+            $this->a = $this->alpha2gd($color[3]);
+        }
+    }
+
+    /**
+     * Initiates color object from given R, G and B values
+     *
+     * @param  integer $r
+     * @param  integer $g
+     * @param  integer $b
+     * @return \Intervention\Image\AbstractColor
+     */
+    public function initFromRgb($r, $g, $b)
+    {
+        $this->r = intval($r);
+        $this->g = intval($g);
+        $this->b = intval($b);
+        $this->a = 0;
+    }
+
+    /**
+     * Initiates color object from given R, G, B and A values
+     *
+     * @param  integer $r
+     * @param  integer $g
+     * @param  integer $b
+     * @param  float   $a
+     * @return \Intervention\Image\AbstractColor
+     */
+    public function initFromRgba($r, $g, $b, $a = 1)
+    {
+        $this->r = intval($r);
+        $this->g = intval($g);
+        $this->b = intval($b);
+        $this->a = $this->alpha2gd($a);
+    }
+
+    /**
+     * Initiates color object from given ImagickPixel object
+     *
+     * @param  ImagickPixel $value
+     * @return \Intervention\Image\AbstractColor
+     */
+    public function initFromObject($value)
+    {
+        throw new \Intervention\Image\Exception\NotSupportedException(
+            "GD colors cannot init from ImagickPixel objects."
+        );
+    }
+
+    /**
+     * Calculates integer value of current color instance
+     *
+     * @return integer
+     */
+    public function getInt()
+    {
+        return ($this->a << 24) + ($this->r << 16) + ($this->g << 8) + $this->b;
+    }
+
+    /**
+     * Calculates hexadecimal value of current color instance
+     *
+     * @param  string $prefix
+     * @return string
+     */
+    public function getHex($prefix = '')
+    {
+        return sprintf('%s%02x%02x%02x', $prefix, $this->r, $this->g, $this->b);
+    }
+
+    /**
+     * Calculates RGB(A) in array format of current color instance
+     *
+     * @return array
+     */
+    public function getArray()
+    {
+        return array($this->r, $this->g, $this->b, round(1 - $this->a / 127, 2));
+    }
+
+    /**
+     * Calculates RGBA in string format of current color instance
+     *
+     * @return string
+     */
+    public function getRgba()
+    {
+        return sprintf('rgba(%d, %d, %d, %.2f)', $this->r, $this->g, $this->b, round(1 - $this->a / 127, 2));
+    }
+
+    /**
+     * Determines if current color is different from given color
+     *
+     * @param  AbstractColor $color
+     * @param  integer       $tolerance
+     * @return boolean
+     */
+    public function differs(AbstractColor $color, $tolerance = 0)
+    {
+        $color_tolerance = round($tolerance * 2.55);
+        $alpha_tolerance = round($tolerance * 1.27);
+
+        $delta = array(
+            'r' => abs($color->r - $this->r),
+            'g' => abs($color->g - $this->g),
+            'b' => abs($color->b - $this->b),
+            'a' => abs($color->a - $this->a)
+        );
+
+        return (
+            $delta['r'] > $color_tolerance or
+            $delta['g'] > $color_tolerance or
+            $delta['b'] > $color_tolerance or
+            $delta['a'] > $alpha_tolerance
+        );
+    }
+
+    /**
+     * Convert rgba alpha (0-1) value to gd value (0-127)
+     *
+     * @param  float $input
+     * @return int
+     */
+    private function alpha2gd($input)
+    {
+        $oldMin = 0;
+        $oldMax = 1;
+
+        $newMin = 127;
+        $newMax = 0;
+
+        return ceil(((($input- $oldMin) * ($newMax - $newMin)) / ($oldMax - $oldMin)) + $newMin);
+    }
+}

+ 28 - 0
plugins/intervention-image/Intervention/Image/Gd/Commands/BackupCommand.php

@@ -0,0 +1,28 @@
+<?php
+
+namespace Intervention\Image\Gd\Commands;
+
+class BackupCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Saves a backups of current state of image core
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $backupName = $this->argument(0)->value();
+
+        // clone current image resource
+        $size = $image->getSize();
+        $clone = imagecreatetruecolor($size->width, $size->height);
+        imagealphablending($clone, false);
+        imagesavealpha($clone, true);
+        imagecopy($clone, $image->getCore(), 0, 0, 0, 0, $size->width, $size->height);
+
+        $image->setBackup($clone, $backupName);
+
+        return true;
+    }
+}

+ 23 - 0
plugins/intervention-image/Intervention/Image/Gd/Commands/BlurCommand.php

@@ -0,0 +1,23 @@
+<?php
+
+namespace Intervention\Image\Gd\Commands;
+
+class BlurCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Applies blur effect on image
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $amount = $this->argument(0)->between(0, 100)->value(1);
+
+        for ($i=0; $i < intval($amount); $i++) {
+            imagefilter($image->getCore(), IMG_FILTER_GAUSSIAN_BLUR);
+        }
+
+        return true;
+    }
+}

+ 19 - 0
plugins/intervention-image/Intervention/Image/Gd/Commands/BrightnessCommand.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace Intervention\Image\Gd\Commands;
+
+class BrightnessCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Changes image brightness
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $level = $this->argument(0)->between(-100, 100)->required()->value();
+
+        return imagefilter($image->getCore(), IMG_FILTER_BRIGHTNESS, ($level * 2.55));
+    }
+}

+ 27 - 0
plugins/intervention-image/Intervention/Image/Gd/Commands/ColorizeCommand.php

@@ -0,0 +1,27 @@
+<?php
+
+namespace Intervention\Image\Gd\Commands;
+
+class ColorizeCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Changes balance of different RGB color channels
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $red = $this->argument(0)->between(-100, 100)->required()->value();
+        $green = $this->argument(1)->between(-100, 100)->required()->value();
+        $blue = $this->argument(2)->between(-100, 100)->required()->value();
+
+        // normalize colorize levels
+        $red = round($red * 2.55);
+        $green = round($green * 2.55);
+        $blue = round($blue * 2.55);
+
+        // apply filter
+        return imagefilter($image->getCore(), IMG_FILTER_COLORIZE, $red, $green, $blue);
+    }
+}

+ 19 - 0
plugins/intervention-image/Intervention/Image/Gd/Commands/ContrastCommand.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace Intervention\Image\Gd\Commands;
+
+class ContrastCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Changes contrast of image
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $level = $this->argument(0)->between(-100, 100)->required()->value();
+
+        return imagefilter($image->getCore(), IMG_FILTER_CONTRAST, ($level * -1));
+    }
+}

+ 40 - 0
plugins/intervention-image/Intervention/Image/Gd/Commands/CropCommand.php

@@ -0,0 +1,40 @@
+<?php
+
+namespace Intervention\Image\Gd\Commands;
+
+use \Intervention\Image\Point;
+use \Intervention\Image\Size;
+
+class CropCommand extends ResizeCommand
+{
+    /**
+     * Crop an image instance
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $width = $this->argument(0)->type('digit')->required()->value();
+        $height = $this->argument(1)->type('digit')->required()->value();
+        $x = $this->argument(2)->type('digit')->value();
+        $y = $this->argument(3)->type('digit')->value();
+
+        if (is_null($width) || is_null($height)) {
+            throw new \Intervention\Image\Exception\InvalidArgumentException(
+                "Width and height of cutout needs to be defined."
+            );
+        }
+
+        $cropped = new Size($width, $height);
+        $position = new Point($x, $y);
+
+        // align boxes
+        if (is_null($x) && is_null($y)) {
+            $position = $image->getSize()->align('center')->relativePosition($cropped->align('center'));
+        }
+
+        // crop image core
+        return $this->modify($image, 0, 0, $position->x, $position->y, $cropped->width, $cropped->height, $cropped->width, $cropped->height);
+    }
+}

+ 25 - 0
plugins/intervention-image/Intervention/Image/Gd/Commands/DestroyCommand.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace Intervention\Image\Gd\Commands;
+
+class DestroyCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Destroys current image core and frees up memory
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        // destroy image core
+        imagedestroy($image->getCore());
+
+        // destroy backups    
+        foreach ($image->getBackups() as $backup) {
+            imagedestroy($backup);
+        }
+
+        return true;
+    }
+}

+ 68 - 0
plugins/intervention-image/Intervention/Image/Gd/Commands/FillCommand.php

@@ -0,0 +1,68 @@
+<?php
+
+namespace Intervention\Image\Gd\Commands;
+
+use Intervention\Image\Gd\Decoder;
+use Intervention\Image\Gd\Color;
+
+class FillCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Fills image with color or pattern
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $filling = $this->argument(0)->value();
+        $x = $this->argument(1)->type('digit')->value();
+        $y = $this->argument(2)->type('digit')->value();
+
+        $width = $image->getWidth();
+        $height = $image->getHeight();
+        $resource = $image->getCore();
+
+        try {
+
+            // set image tile filling
+            $source = new Decoder;
+            $tile = $source->init($filling);
+            imagesettile($image->getCore(), $tile->getCore());
+            $filling = IMG_COLOR_TILED;
+
+        } catch (\Intervention\Image\Exception\NotReadableException $e) {
+
+            // set solid color filling
+            $color = new Color($filling);
+            $filling = $color->getInt();
+        }
+
+        imagealphablending($resource, true);
+
+        if (is_int($x) && is_int($y)) {
+
+            // resource should be visible through transparency
+            $base = $image->getDriver()->newImage($width, $height)->getCore();
+            imagecopy($base, $resource, 0, 0, 0, 0, $width, $height);
+
+            // floodfill if exact position is defined
+            imagefill($resource, $x, $y, $filling);
+
+            // copy filled original over base
+            imagecopy($base, $resource, 0, 0, 0, 0, $width, $height);
+
+            // set base as new resource-core
+            $image->setCore($base);
+            imagedestroy($resource);
+
+        } else {
+            // fill whole image otherwise
+            imagefilledrectangle($resource, 0, 0, $width - 1, $height - 1, $filling);
+        }
+
+        isset($tile) ? imagedestroy($tile->getCore()) : null;
+
+        return true;
+    }
+}

+ 33 - 0
plugins/intervention-image/Intervention/Image/Gd/Commands/FitCommand.php

@@ -0,0 +1,33 @@
+<?php
+
+namespace Intervention\Image\Gd\Commands;
+
+use \Intervention\Image\Point;
+use \Intervention\Image\Size;
+
+class FitCommand extends ResizeCommand
+{
+    /**
+     * Crops and resized an image at the same time
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $width = $this->argument(0)->type('digit')->required()->value();
+        $height = $this->argument(1)->type('digit')->value($width);
+        $constraints = $this->argument(2)->type('closure')->value();
+        $position = $this->argument(3)->type('string')->value('center');
+
+        // calculate size
+        $cropped = $image->getSize()->fit(new Size($width, $height), $position);
+        $resized = clone $cropped;
+        $resized = $resized->resize($width, $height, $constraints);
+
+        // modify image
+        $this->modify($image, 0, 0, $cropped->pivot->x, $cropped->pivot->y, $resized->getWidth(), $resized->getHeight(), $cropped->getWidth(), $cropped->getHeight());
+
+        return true;
+    }
+}

+ 37 - 0
plugins/intervention-image/Intervention/Image/Gd/Commands/FlipCommand.php

@@ -0,0 +1,37 @@
+<?php
+
+namespace Intervention\Image\Gd\Commands;
+
+class FlipCommand extends ResizeCommand
+{
+    /**
+     * Mirrors an image
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $mode = $this->argument(0)->value('h');
+
+        $size = $image->getSize();
+        $dst = clone $size;
+
+        switch (strtolower($mode)) {
+            case 2:
+            case 'v':
+            case 'vert':
+            case 'vertical':
+                $size->pivot->y = $size->height - 1;
+                $size->height = $size->height * (-1);
+                break;
+
+            default:
+                $size->pivot->x = $size->width - 1;
+                $size->width = $size->width * (-1);
+                break;
+        }
+
+        return $this->modify($image, 0, 0, $size->pivot->x, $size->pivot->y, $dst->width, $dst->height, $size->width, $size->height);
+    }
+}

+ 19 - 0
plugins/intervention-image/Intervention/Image/Gd/Commands/GammaCommand.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace Intervention\Image\Gd\Commands;
+
+class GammaCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Applies gamma correction to a given image
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $gamma = $this->argument(0)->type('numeric')->required()->value();
+
+        return imagegammacorrect($image->getCore(), 1, $gamma);
+    }
+}

+ 24 - 0
plugins/intervention-image/Intervention/Image/Gd/Commands/GetSizeCommand.php

@@ -0,0 +1,24 @@
+<?php
+
+namespace Intervention\Image\Gd\Commands;
+
+use \Intervention\Image\Size;
+
+class GetSizeCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Reads size of given image instance in pixels
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $this->setOutput(new Size(
+            imagesx($image->getCore()),
+            imagesy($image->getCore())
+        ));
+
+        return true;
+    }
+}

+ 17 - 0
plugins/intervention-image/Intervention/Image/Gd/Commands/GreyscaleCommand.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace Intervention\Image\Gd\Commands;
+
+class GreyscaleCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Turns an image into a greyscale version
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        return imagefilter($image->getCore(), IMG_FILTER_GRAYSCALE);
+    }
+}

+ 28 - 0
plugins/intervention-image/Intervention/Image/Gd/Commands/HeightenCommand.php

@@ -0,0 +1,28 @@
+<?php
+
+namespace Intervention\Image\Gd\Commands;
+
+class HeightenCommand extends ResizeCommand
+{
+    /**
+     * Resize image proportionally to given height
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $height = $this->argument(0)->type('digit')->required()->value();
+        $additionalConstraints = $this->argument(1)->type('closure')->value();
+
+        $this->arguments[0] = null;
+        $this->arguments[1] = $height;
+        $this->arguments[2] = function ($constraint) use ($additionalConstraints) {
+            $constraint->aspectRatio();
+            if(is_callable($additionalConstraints)) 
+                $additionalConstraints($constraint);
+        };
+
+        return parent::execute($image);
+    }
+}

+ 32 - 0
plugins/intervention-image/Intervention/Image/Gd/Commands/InsertCommand.php

@@ -0,0 +1,32 @@
+<?php
+
+namespace Intervention\Image\Gd\Commands;
+
+class InsertCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Insert another image into given image
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $source = $this->argument(0)->required()->value();
+        $position = $this->argument(1)->type('string')->value();
+        $x = $this->argument(2)->type('digit')->value(0);
+        $y = $this->argument(3)->type('digit')->value(0);
+
+        // build watermark
+        $watermark = $image->getDriver()->init($source);
+
+        // define insertion point
+        $image_size = $image->getSize()->align($position, $x, $y);
+        $watermark_size = $watermark->getSize()->align($position);
+        $target = $image_size->relativePosition($watermark_size);
+
+        // insert image at position
+        imagealphablending($image->getCore(), true);
+        return imagecopy($image->getCore(), $watermark->getCore(), $target->x, $target->y, 0, 0, $watermark_size->width, $watermark_size->height);
+    }
+}

+ 21 - 0
plugins/intervention-image/Intervention/Image/Gd/Commands/InterlaceCommand.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace Intervention\Image\Gd\Commands;
+
+class InterlaceCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Toggles interlaced encoding mode
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $mode = $this->argument(0)->type('bool')->value(true);
+
+        imageinterlace($image->getCore(), $mode);
+
+        return true;
+    }
+}

+ 17 - 0
plugins/intervention-image/Intervention/Image/Gd/Commands/InvertCommand.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace Intervention\Image\Gd\Commands;
+
+class InvertCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Inverts colors of an image
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        return imagefilter($image->getCore(), IMG_FILTER_NEGATE);
+    }
+}

+ 52 - 0
plugins/intervention-image/Intervention/Image/Gd/Commands/LimitColorsCommand.php

@@ -0,0 +1,52 @@
+<?php
+
+namespace Intervention\Image\Gd\Commands;
+
+use Intervention\Image\Gd\Color;
+
+class LimitColorsCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Reduces colors of a given image
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $count = $this->argument(0)->value();
+        $matte = $this->argument(1)->value();
+
+        // get current image size
+        $size = $image->getSize();
+
+        // create empty canvas
+        $resource = imagecreatetruecolor($size->width, $size->height);
+
+        // define matte
+        if (is_null($matte)) {
+            $matte = imagecolorallocatealpha($resource, 255, 255, 255, 127);
+        } else {
+            $matte = $image->getDriver()->parseColor($matte)->getInt();
+        }
+
+        // fill with matte and copy original image
+        imagefill($resource, 0, 0, $matte);
+
+        // set transparency
+        imagecolortransparent($resource, $matte);
+
+        // copy original image
+        imagecopy($resource, $image->getCore(), 0, 0, 0, 0, $size->width, $size->height);
+
+        if (is_numeric($count) && $count <= 256) {
+            // decrease colors
+            imagetruecolortopalette($resource, true, $count);
+        }
+
+        // set new resource
+        $image->setCore($resource);
+
+        return true;
+    }
+}

+ 81 - 0
plugins/intervention-image/Intervention/Image/Gd/Commands/MaskCommand.php

@@ -0,0 +1,81 @@
+<?php
+
+namespace Intervention\Image\Gd\Commands;
+
+class MaskCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Applies an alpha mask to an image
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $mask_source = $this->argument(0)->value();
+        $mask_w_alpha = $this->argument(1)->type('bool')->value(false);
+
+        $image_size = $image->getSize();
+
+        // create empty canvas
+        $canvas = $image->getDriver()->newImage($image_size->width, $image_size->height, array(0,0,0,0));
+
+        // build mask image from source
+        $mask = $image->getDriver()->init($mask_source);
+        $mask_size = $mask->getSize();
+
+        // resize mask to size of current image (if necessary)
+        if ($mask_size != $image_size) {
+            $mask->resize($image_size->width, $image_size->height);
+        }
+
+        imagealphablending($canvas->getCore(), false);
+
+        if ( ! $mask_w_alpha) {
+            // mask from greyscale image
+            imagefilter($mask->getCore(), IMG_FILTER_GRAYSCALE);
+        }
+
+        // redraw old image pixel by pixel considering alpha map
+        for ($x=0; $x < $image_size->width; $x++) {
+            for ($y=0; $y < $image_size->height; $y++) {
+
+                $color = $image->pickColor($x, $y, 'array');
+                $alpha = $mask->pickColor($x, $y, 'array');
+
+                if ($mask_w_alpha) {
+                    $alpha = $alpha[3]; // use alpha channel as mask
+                } else {
+
+                    if ($alpha[3] == 0) { // transparent as black
+                        $alpha = 0;
+                    } else {
+
+                        // $alpha = floatval(round((($alpha[0] + $alpha[1] + $alpha[3]) / 3) / 255, 2));
+
+                        // image is greyscale, so channel doesn't matter (use red channel)
+                        $alpha = floatval(round($alpha[0] / 255, 2));
+                    }
+
+                }
+
+                // preserve alpha of original image...
+                if ($color[3] < $alpha) {
+                    $alpha = $color[3];
+                }
+
+                // replace alpha value
+                $color[3] = $alpha;
+
+                // redraw pixel
+                $canvas->pixel($color, $x, $y);
+            }
+        }
+
+
+        // replace current image with masked instance
+        $image->setCore($canvas->getCore());
+
+        return true;
+    }
+}

+ 29 - 0
plugins/intervention-image/Intervention/Image/Gd/Commands/OpacityCommand.php

@@ -0,0 +1,29 @@
+<?php
+
+namespace Intervention\Image\Gd\Commands;
+
+class OpacityCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Defines opacity of an image
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $transparency = $this->argument(0)->between(0, 100)->required()->value();
+
+        // get size of image
+        $size = $image->getSize();
+
+        // build temp alpha mask
+        $mask_color = sprintf('rgba(0, 0, 0, %.1f)', $transparency / 100);
+        $mask = $image->getDriver()->newImage($size->width, $size->height, $mask_color);
+
+        // mask image
+        $image->mask($mask->getCore(), true);
+
+        return true;
+    }
+}

+ 36 - 0
plugins/intervention-image/Intervention/Image/Gd/Commands/PickColorCommand.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace Intervention\Image\Gd\Commands;
+
+use \Intervention\Image\Gd\Color;
+
+class PickColorCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Read color information from a certain position
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $x = $this->argument(0)->type('digit')->required()->value();
+        $y = $this->argument(1)->type('digit')->required()->value();
+        $format = $this->argument(2)->type('string')->value('array');
+
+        // pick color
+        $color = imagecolorat($image->getCore(), $x, $y);
+
+        if ( ! imageistruecolor($image->getCore())) {
+            $color = imagecolorsforindex($image->getCore(), $color);
+            $color['alpha'] = round(1 - $color['alpha'] / 127, 2);
+        }
+
+        $color = new Color($color);
+
+        // format to output
+        $this->setOutput($color->format($format));
+
+        return true;
+    }
+}

+ 24 - 0
plugins/intervention-image/Intervention/Image/Gd/Commands/PixelCommand.php

@@ -0,0 +1,24 @@
+<?php
+
+namespace Intervention\Image\Gd\Commands;
+
+use \Intervention\Image\Gd\Color;
+
+class PixelCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Draws one pixel to a given image
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $color = $this->argument(0)->required()->value();
+        $color = new Color($color);
+        $x = $this->argument(1)->type('digit')->required()->value();
+        $y = $this->argument(2)->type('digit')->required()->value();
+
+        return imagesetpixel($image->getCore(), $x, $y, $color->getInt());
+    }
+}

+ 19 - 0
plugins/intervention-image/Intervention/Image/Gd/Commands/PixelateCommand.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace Intervention\Image\Gd\Commands;
+
+class PixelateCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Applies a pixelation effect to a given image
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $size = $this->argument(0)->type('digit')->value(10);
+
+        return imagefilter($image->getCore(), IMG_FILTER_PIXELATE, $size, true);
+    }
+}

+ 35 - 0
plugins/intervention-image/Intervention/Image/Gd/Commands/ResetCommand.php

@@ -0,0 +1,35 @@
+<?php
+
+namespace Intervention\Image\Gd\Commands;
+
+class ResetCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Resets given image to its backup state
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $backupName = $this->argument(0)->value();
+
+        if (is_resource($backup = $image->getBackup($backupName))) {
+
+            // destroy current resource
+            imagedestroy($image->getCore());
+
+            // clone backup
+            $backup = $image->getDriver()->cloneCore($backup);
+
+            // reset to new resource
+            $image->setCore($backup);
+
+            return true;
+        }
+
+        throw new \Intervention\Image\Exception\RuntimeException(
+            "Backup not available. Call backup() before reset()."
+        );
+    }
+}

+ 84 - 0
plugins/intervention-image/Intervention/Image/Gd/Commands/ResizeCanvasCommand.php

@@ -0,0 +1,84 @@
+<?php
+
+namespace Intervention\Image\Gd\Commands;
+
+use \Intervention\Image\Point;
+use \Intervention\Image\Size;
+
+class ResizeCanvasCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Resizes image boundaries
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $width = $this->argument(0)->type('digit')->required()->value();
+        $height = $this->argument(1)->type('digit')->required()->value();
+        $anchor = $this->argument(2)->value('center');
+        $relative = $this->argument(3)->type('boolean')->value();
+        $bgcolor = $this->argument(4)->value();
+
+        $original_width = $image->getWidth();
+        $original_height = $image->getHeight();
+
+        // check of only width or height is set
+        $width = is_null($width) ? $original_width : intval($width);
+        $height = is_null($height) ? $original_height : intval($height);
+
+        // check on relative width/height
+        if ($relative) {
+            $width = $original_width + $width;
+            $height = $original_height + $height;
+        }
+
+        // check for negative width/height
+        $width = ($width <= 0) ? $width + $original_width : $width;
+        $height = ($height <= 0) ? $height + $original_height : $height;
+
+        // create new canvas
+        $canvas = $image->getDriver()->newImage($width, $height, $bgcolor);
+
+        // set copy position
+        $canvas_size = $canvas->getSize()->align($anchor);
+        $image_size = $image->getSize()->align($anchor);
+        $canvas_pos = $image_size->relativePosition($canvas_size);
+        $image_pos = $canvas_size->relativePosition($image_size);
+
+        if ($width <= $original_width) {
+            $dst_x = 0;
+            $src_x = $canvas_pos->x;
+            $src_w = $canvas_size->width;
+        } else {
+            $dst_x = $image_pos->x;
+            $src_x = 0;
+            $src_w = $original_width;
+        }
+
+        if ($height <= $original_height) {
+            $dst_y = 0;
+            $src_y = $canvas_pos->y;
+            $src_h = $canvas_size->height;
+        } else {
+            $dst_y = $image_pos->y;
+            $src_y = 0;
+            $src_h = $original_height;
+        }
+
+        // make image area transparent to keep transparency
+        // even if background-color is set
+        $transparent = imagecolorallocatealpha($canvas->getCore(), 255, 255, 255, 127);
+        imagealphablending($canvas->getCore(), false); // do not blend / just overwrite
+        imagefilledrectangle($canvas->getCore(), $dst_x, $dst_y, $dst_x + $src_w - 1, $dst_y + $src_h - 1, $transparent);
+
+        // copy image into new canvas
+        imagecopy($canvas->getCore(), $image->getCore(), $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h);
+
+        // set new core to canvas
+        $image->setCore($canvas->getCore());
+
+        return true;
+    }
+}

+ 82 - 0
plugins/intervention-image/Intervention/Image/Gd/Commands/ResizeCommand.php

@@ -0,0 +1,82 @@
+<?php
+
+namespace Intervention\Image\Gd\Commands;
+
+class ResizeCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Resizes image dimensions
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $width = $this->argument(0)->value();
+        $height = $this->argument(1)->value();
+        $constraints = $this->argument(2)->type('closure')->value();
+
+        // resize box
+        $resized = $image->getSize()->resize($width, $height, $constraints);
+
+        // modify image
+        $this->modify($image, 0, 0, 0, 0, $resized->getWidth(), $resized->getHeight(), $image->getWidth(), $image->getHeight());
+
+        return true;
+    }
+
+    /**
+     * Wrapper function for 'imagecopyresampled'
+     *
+     * @param  Image   $image
+     * @param  integer $dst_x
+     * @param  integer $dst_y
+     * @param  integer $src_x
+     * @param  integer $src_y
+     * @param  integer $dst_w
+     * @param  integer $dst_h
+     * @param  integer $src_w
+     * @param  integer $src_h
+     * @return boolean
+     */
+    protected function modify($image, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h)
+    {
+        // create new image
+        $modified = imagecreatetruecolor($dst_w, $dst_h);
+
+        // get current image
+        $resource = $image->getCore();
+
+        // preserve transparency
+        $transIndex = imagecolortransparent($resource);
+
+        if ($transIndex != -1) {
+            $rgba = imagecolorsforindex($modified, $transIndex);
+            $transColor = imagecolorallocatealpha($modified, $rgba['red'], $rgba['green'], $rgba['blue'], 127);
+            imagefill($modified, 0, 0, $transColor);
+            imagecolortransparent($modified, $transColor);
+        } else {
+            imagealphablending($modified, false);
+            imagesavealpha($modified, true);
+        }
+
+        // copy content from resource
+        $result = imagecopyresampled(
+            $modified,
+            $resource,
+            $dst_x,
+            $dst_y,
+            $src_x,
+            $src_y,
+            $dst_w,
+            $dst_h,
+            $src_w,
+            $src_h
+        );
+
+        // set new content as recource
+        $image->setCore($modified);
+
+        return $result;
+    }
+}

+ 26 - 0
plugins/intervention-image/Intervention/Image/Gd/Commands/RotateCommand.php

@@ -0,0 +1,26 @@
+<?php
+
+namespace Intervention\Image\Gd\Commands;
+
+use \Intervention\Image\Gd\Color;
+
+class RotateCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Rotates image counter clockwise
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $angle = $this->argument(0)->type('numeric')->required()->value();
+        $color = $this->argument(1)->value();
+        $color = new Color($color);
+
+        // rotate image
+        $image->setCore(imagerotate($image->getCore(), $angle, $color->getInt()));
+
+        return true;
+    }
+}

+ 32 - 0
plugins/intervention-image/Intervention/Image/Gd/Commands/SharpenCommand.php

@@ -0,0 +1,32 @@
+<?php
+
+namespace Intervention\Image\Gd\Commands;
+
+class SharpenCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Sharpen image
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $amount = $this->argument(0)->between(0, 100)->value(10);
+
+        // build matrix
+        $min = $amount >= 10 ? $amount * -0.01 : 0;
+        $max = $amount * -0.025;
+        $abs = ((4 * $min + 4 * $max) * -1) + 1;
+        $div = 1;
+
+        $matrix = array(
+            array($min, $max, $min),
+            array($max, $abs, $max),
+            array($min, $max, $min)
+        );
+
+        // apply the matrix
+        return imageconvolution($image->getCore(), $matrix, $div, 0);
+    }
+}

+ 176 - 0
plugins/intervention-image/Intervention/Image/Gd/Commands/TrimCommand.php

@@ -0,0 +1,176 @@
+<?php
+
+namespace Intervention\Image\Gd\Commands;
+
+use \Intervention\Image\Gd\Color;
+
+class TrimCommand extends ResizeCommand
+{
+    /**
+     * Trims away parts of an image
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $base = $this->argument(0)->type('string')->value();
+        $away = $this->argument(1)->value();
+        $tolerance = $this->argument(2)->type('numeric')->value(0);
+        $feather = $this->argument(3)->type('numeric')->value(0);
+
+        $width = $image->getWidth();
+        $height = $image->getHeight();
+
+        // default values
+        $checkTransparency = false;
+
+        // define borders to trim away
+        if (is_null($away)) {
+            $away = array('top', 'right', 'bottom', 'left');
+        } elseif (is_string($away)) {
+            $away = array($away);
+        }
+
+        // lower border names
+        foreach ($away as $key => $value) {
+            $away[$key] = strtolower($value);
+        }
+
+        // define base color position
+        switch (strtolower($base)) {
+            case 'transparent':
+            case 'trans':
+                $checkTransparency = true;
+                $base_x = 0;
+                $base_y = 0;
+                break;
+
+            case 'bottom-right':
+            case 'right-bottom':
+                $base_x = $width - 1;
+                $base_y = $height - 1;
+                break;
+
+            default:
+            case 'top-left':
+            case 'left-top':
+                $base_x = 0;
+                $base_y = 0;
+                break;
+        }
+
+        // pick base color
+        if ($checkTransparency) {
+            $color = new Color; // color will only be used to compare alpha channel
+        } else {
+            $color = $image->pickColor($base_x, $base_y, 'object');
+        }
+
+        $top_x = 0;
+        $top_y = 0;
+        $bottom_x = $width;
+        $bottom_y = $height;
+
+        // search upper part of image for colors to trim away
+        if (in_array('top', $away)) {
+
+            for ($y=0; $y < ceil($height/2); $y++) {
+                for ($x=0; $x < $width; $x++) {
+
+                    $checkColor = $image->pickColor($x, $y, 'object');
+
+                    if ($checkTransparency) {
+                        $checkColor->r = $color->r;
+                        $checkColor->g = $color->g;
+                        $checkColor->b = $color->b;
+                    }
+
+                    if ($color->differs($checkColor, $tolerance)) {
+                        $top_y = max(0, $y - $feather);
+                        break 2;
+                    }
+
+                }
+            }
+
+        }
+
+        // search left part of image for colors to trim away
+        if (in_array('left', $away)) {
+
+            for ($x=0; $x < ceil($width/2); $x++) {
+                for ($y=$top_y; $y < $height; $y++) {
+
+                    $checkColor = $image->pickColor($x, $y, 'object');
+
+                    if ($checkTransparency) {
+                        $checkColor->r = $color->r;
+                        $checkColor->g = $color->g;
+                        $checkColor->b = $color->b;
+                    }
+
+                    if ($color->differs($checkColor, $tolerance)) {
+                        $top_x = max(0, $x - $feather);
+                        break 2;
+                    }
+
+                }
+            }
+
+        }
+
+        // search lower part of image for colors to trim away
+        if (in_array('bottom', $away)) {
+
+            for ($y=($height-1); $y >= floor($height/2)-1; $y--) {
+                for ($x=$top_x; $x < $width; $x++) {
+
+                    $checkColor = $image->pickColor($x, $y, 'object');
+
+                    if ($checkTransparency) {
+                        $checkColor->r = $color->r;
+                        $checkColor->g = $color->g;
+                        $checkColor->b = $color->b;
+                    }
+
+                    if ($color->differs($checkColor, $tolerance)) {
+                        $bottom_y = min($height, $y+1 + $feather);
+                        break 2;
+                    }
+
+                }
+            }
+
+        }
+
+        // search right part of image for colors to trim away
+        if (in_array('right', $away)) {
+
+            for ($x=($width-1); $x >= floor($width/2)-1; $x--) {
+                for ($y=$top_y; $y < $bottom_y; $y++) {
+
+                    $checkColor = $image->pickColor($x, $y, 'object');
+
+                    if ($checkTransparency) {
+                        $checkColor->r = $color->r;
+                        $checkColor->g = $color->g;
+                        $checkColor->b = $color->b;
+                    }
+
+                    if ($color->differs($checkColor, $tolerance)) {
+                        $bottom_x = min($width, $x+1 + $feather);
+                        break 2;
+                    }
+
+                }
+            }
+
+        }
+
+
+        // trim parts of image
+        return $this->modify($image, 0, 0, $top_x, $top_y, ($bottom_x-$top_x), ($bottom_y-$top_y), ($bottom_x-$top_x), ($bottom_y-$top_y));
+
+    }
+}

+ 28 - 0
plugins/intervention-image/Intervention/Image/Gd/Commands/WidenCommand.php

@@ -0,0 +1,28 @@
+<?php
+
+namespace Intervention\Image\Gd\Commands;
+
+class WidenCommand extends ResizeCommand
+{
+    /**
+     * Resize image proportionally to given width
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $width = $this->argument(0)->type('digit')->required()->value();
+        $additionalConstraints = $this->argument(1)->type('closure')->value();
+
+        $this->arguments[0] = $width;
+        $this->arguments[1] = null;
+        $this->arguments[2] = function ($constraint) use ($additionalConstraints) {
+            $constraint->aspectRatio();
+            if(is_callable($additionalConstraints)) 
+                $additionalConstraints($constraint);
+        };
+
+        return parent::execute($image);
+    }
+}

+ 132 - 0
plugins/intervention-image/Intervention/Image/Gd/Decoder.php

@@ -0,0 +1,132 @@
+<?php
+
+namespace Intervention\Image\Gd;
+
+use \Intervention\Image\Image;
+use \Intervention\Image\Size;
+
+class Decoder extends \Intervention\Image\AbstractDecoder
+{
+    /**
+     * Initiates new image from path in filesystem
+     *
+     * @param  string $path
+     * @return \Intervention\Image\Image
+     */
+    public function initFromPath($path)
+    {
+        $info = @getimagesize($path);
+
+        if ($info === false) {
+            throw new \Intervention\Image\Exception\NotReadableException(
+                "Unable to read image from file ({$path})."
+            );
+        }
+
+        // define core
+        switch ($info[2]) {
+            case IMAGETYPE_PNG:
+                $core = imagecreatefrompng($path);
+                $this->gdResourceToTruecolor($core);
+                break;
+
+            case IMAGETYPE_JPEG:
+                $core = imagecreatefromjpeg($path);
+                $this->gdResourceToTruecolor($core);
+                break;
+
+            case IMAGETYPE_GIF:
+                $core = imagecreatefromgif($path);
+                $this->gdResourceToTruecolor($core);
+                break;
+
+            default:
+                throw new \Intervention\Image\Exception\NotReadableException(
+                    "Unable to read image type. GD driver is only able to decode JPG, PNG or GIF files."
+                );
+        }
+
+        // build image
+        $image = $this->initFromGdResource($core);
+        $image->mime = $info['mime'];
+        $image->setFileInfoFromPath($path);
+
+        return $image;
+    }
+
+    /**
+     * Initiates new image from GD resource
+     *
+     * @param  Resource $resource
+     * @return \Intervention\Image\Image
+     */
+    public function initFromGdResource($resource)
+    {
+        return new Image(new Driver, $resource);
+    }
+
+    /**
+     * Initiates new image from Imagick object
+     *
+     * @param  Imagick $object
+     * @return \Intervention\Image\Image
+     */
+    public function initFromImagick(\Imagick $object)
+    {
+        throw new \Intervention\Image\Exception\NotSupportedException(
+            "Gd driver is unable to init from Imagick object."
+        );
+    }
+
+    /**
+     * Initiates new image from binary data
+     *
+     * @param  string $data
+     * @return \Intervention\Image\Image
+     */
+    public function initFromBinary($binary)
+    {
+        $resource = @imagecreatefromstring($binary);
+
+        if ($resource === false) {
+             throw new \Intervention\Image\Exception\NotReadableException(
+                "Unable to init from given binary data."
+            );
+        }
+
+        $image = $this->initFromGdResource($resource);
+        $image->mime = finfo_buffer(finfo_open(FILEINFO_MIME_TYPE), $binary);
+
+        return $image;
+    }
+
+    /**
+     * Transform GD resource into Truecolor version
+     *
+     * @param  resource $resource
+     * @return bool
+     */
+    public function gdResourceToTruecolor(&$resource)
+    {
+        $width = imagesx($resource);
+        $height = imagesy($resource);
+
+        // new canvas
+        $canvas = imagecreatetruecolor($width, $height);
+
+        // fill with transparent color
+        imagealphablending($canvas, false);
+        $transparent = imagecolorallocatealpha($canvas, 255, 255, 255, 127);
+        imagefilledrectangle($canvas, 0, 0, $width, $height, $transparent);
+        imagecolortransparent($canvas, $transparent);
+        imagealphablending($canvas, true);
+
+        // copy original
+        imagecopy($canvas, $resource, 0, 0, 0, 0, $width, $height);
+        imagedestroy($resource);
+
+        $resource = $canvas;
+
+        return true;
+    }
+}

+ 86 - 0
plugins/intervention-image/Intervention/Image/Gd/Driver.php

@@ -0,0 +1,86 @@
+<?php
+
+namespace Intervention\Image\Gd;
+
+use \Intervention\Image\Size;
+
+class Driver extends \Intervention\Image\AbstractDriver
+{
+    /**
+     * Creates new instance of driver
+     *
+     * @param Decoder $decoder
+     * @param Encoder $encoder
+     */
+    public function __construct(Decoder $decoder = null, Encoder $encoder = null)
+    {
+        if ( ! $this->coreAvailable()) {
+            throw new \Intervention\Image\Exception\NotSupportedException(
+                "GD Library extension not available with this PHP installation."
+            );
+        }
+
+        $this->decoder = $decoder ? $decoder : new Decoder;
+        $this->encoder = $encoder ? $encoder : new Encoder;
+    }
+
+    /**
+     * Creates new image instance
+     *
+     * @param  integer $width
+     * @param  integer $height
+     * @param  string  $background
+     * @return \Intervention\Image\Image
+     */
+    public function newImage($width, $height, $background = null)
+    {
+        // create empty resource
+        $core = imagecreatetruecolor($width, $height);
+        $image = new \Intervention\Image\Image(new self, $core);
+
+        // set background color
+        $background = new Color($background);
+        imagefill($image->getCore(), 0, 0, $background->getInt());
+
+        return $image;
+    }
+
+    /**
+     * Reads given string into color object
+     *
+     * @param  string $value
+     * @return AbstractColor
+     */
+    public function parseColor($value)
+    {
+        return new Color($value);
+    }
+
+    /**
+     * Checks if core module installation is available
+     *
+     * @return boolean
+     */
+    protected function coreAvailable()
+    {
+        return (extension_loaded('gd') && function_exists('gd_info'));
+    }
+
+    /**
+     * Returns clone of given core
+     *
+     * @return mixed
+     */
+    public function cloneCore($core)
+    {
+        $width = imagesx($core);
+        $height = imagesy($core);
+        $clone = imagecreatetruecolor($width, $height);
+        imagealphablending($clone, false);
+        imagesavealpha($clone, true);
+        
+        imagecopy($clone, $core, 0, 0, 0, 0, $width, $height);
+
+        return $clone;
+    }
+}

+ 105 - 0
plugins/intervention-image/Intervention/Image/Gd/Encoder.php

@@ -0,0 +1,105 @@
+<?php
+
+namespace Intervention\Image\Gd;
+
+class Encoder extends \Intervention\Image\AbstractEncoder
+{
+    /**
+     * Processes and returns encoded image as JPEG string
+     *
+     * @return string
+     */
+    protected function processJpeg()
+    {
+        ob_start();
+        imagejpeg($this->image->getCore(), null, $this->quality);
+        $this->image->mime = image_type_to_mime_type(IMAGETYPE_JPEG);
+        $buffer = ob_get_contents();
+        ob_end_clean();
+
+        return $buffer;
+    }
+
+    /**
+     * Processes and returns encoded image as PNG string
+     *
+     * @return string
+     */
+    protected function processPng()
+    {
+        ob_start();
+        $resource = $this->image->getCore();
+        imagealphablending($resource, false);
+        imagesavealpha($resource, true);
+        imagepng($resource, null, -1);
+        $this->image->mime = image_type_to_mime_type(IMAGETYPE_PNG);
+        $buffer = ob_get_contents();
+        ob_end_clean();
+
+        return $buffer;
+    }
+
+    /**
+     * Processes and returns encoded image as GIF string
+     *
+     * @return string
+     */
+    protected function processGif()
+    {
+        ob_start();
+        imagegif($this->image->getCore());
+        $this->image->mime = image_type_to_mime_type(IMAGETYPE_GIF);
+        $buffer = ob_get_contents();
+        ob_end_clean();
+
+        return $buffer;
+    }
+
+    /**
+     * Processes and returns encoded image as TIFF string
+     *
+     * @return string
+     */
+    protected function processTiff()
+    {
+        throw new \Intervention\Image\Exception\NotSupportedException(
+            "TIFF format is not supported by Gd Driver."
+        );
+    }
+
+    /**
+     * Processes and returns encoded image as BMP string
+     *
+     * @return string
+     */
+    protected function processBmp()
+    {
+        throw new \Intervention\Image\Exception\NotSupportedException(
+            "BMP format is not supported by Gd Driver."
+        );
+    }
+
+    /**
+     * Processes and returns encoded image as ICO string
+     *
+     * @return string
+     */
+    protected function processIco()
+    {
+        throw new \Intervention\Image\Exception\NotSupportedException(
+            "ICO format is not supported by Gd Driver."
+        );
+    }
+
+    /**
+     * Processes and returns encoded image as PSD string
+     *
+     * @return string
+     */
+    protected function processPsd()
+    {
+        throw new \Intervention\Image\Exception\NotSupportedException(
+            "PSD format is not supported by Gd Driver."
+        );
+    }
+}

+ 255 - 0
plugins/intervention-image/Intervention/Image/Gd/Font.php

@@ -0,0 +1,255 @@
+<?php
+
+namespace Intervention\Image\Gd;
+
+use \Intervention\Image\Image;
+
+class Font extends \Intervention\Image\AbstractFont
+{
+    /**
+     * Get font size in points
+     *
+     * @return integer
+     */
+    protected function getPointSize()
+    {
+        return intval(ceil($this->size * 0.75));
+    }
+
+    /**
+     * Filter function to access internal integer font values
+     *
+     * @return integer
+     */
+    private function getInternalFont()
+    {
+        $internalfont = is_null($this->file) ? 1 : $this->file;
+        $internalfont = is_numeric($internalfont) ? $internalfont : false;
+
+        if ( ! in_array($internalfont, array(1, 2, 3, 4, 5))) {
+            throw new \Intervention\Image\Exception\NotSupportedException(
+                sprintf('Internal GD font (%s) not available. Use only 1-5.', $internalfont)
+            );
+        }
+
+        return intval($internalfont);
+    }
+
+    /**
+     * Get width of an internal font character
+     *
+     * @return integer
+     */
+    private function getInternalFontWidth()
+    {
+        return $this->getInternalFont() + 4;
+    }
+
+    /**
+     * Get height of an internal font character
+     *
+     * @return integer
+     */
+    private function getInternalFontHeight()
+    {
+        switch ($this->getInternalFont()) {
+            case 1:
+                return 8;
+
+            case 2:
+                return 14;
+
+            case 3:
+                return 14;
+
+            case 4:
+                return 16;
+
+            case 5:
+                return 16;
+        }
+    }
+
+    /**
+     * Calculates bounding box of current font setting
+     *
+     * @return Array
+     */
+    public function getBoxSize()
+    {
+        $box = array();
+
+        if ($this->hasApplicableFontFile()) {
+
+            // get bounding box with angle 0
+            $box = imagettfbbox($this->getPointSize(), 0, $this->file, $this->text);
+
+            // rotate points manually
+            if ($this->angle != 0) {
+
+                $angle = pi() * 2 - $this->angle * pi() * 2 / 360;
+
+                for ($i=0; $i<4; $i++) {
+                    $x = $box[$i * 2];
+                    $y = $box[$i * 2 + 1];
+                    $box[$i * 2] = cos($angle) * $x - sin($angle) * $y;
+                    $box[$i * 2 + 1] = sin($angle) * $x + cos($angle) * $y;
+                }
+            }
+
+            $box['width'] = intval(abs($box[4] - $box[0]));
+            $box['height'] = intval(abs($box[5] - $box[1]));
+
+        } else {
+
+            // get current internal font size
+            $width = $this->getInternalFontWidth();
+            $height = $this->getInternalFontHeight();
+
+            if (strlen($this->text) == 0) {
+                // no text -> no boxsize
+                $box['width'] = 0;
+                $box['height'] = 0;
+            } else {
+                // calculate boxsize
+                $box['width'] = strlen($this->text) * $width;
+                $box['height'] = $height;
+            }
+        }
+
+        return $box;
+    }
+
+    /**
+     * Draws font to given image at given position
+     *
+     * @param  Image   $image
+     * @param  integer $posx
+     * @param  integer $posy
+     * @return void
+     */
+    public function applyToImage(Image $image, $posx = 0, $posy = 0)
+    {
+        // parse text color
+        $color = new Color($this->color);
+
+        if ($this->hasApplicableFontFile()) {
+
+            if ($this->angle != 0 || is_string($this->align) || is_string($this->valign)) {
+
+                $box = $this->getBoxSize();
+
+                $align = is_null($this->align) ? 'left' : strtolower($this->align);
+                $valign = is_null($this->valign) ? 'bottom' : strtolower($this->valign);
+
+                // correction on position depending on v/h alignment
+                switch ($align.'-'.$valign) {
+
+                    case 'center-top':
+                        $posx = $posx - round(($box[6]+$box[4])/2);
+                        $posy = $posy - round(($box[7]+$box[5])/2);
+                        break;
+
+                    case 'right-top':
+                        $posx = $posx - $box[4];
+                        $posy = $posy - $box[5];
+                        break;
+
+                    case 'left-top':
+                        $posx = $posx - $box[6];
+                        $posy = $posy - $box[7];
+                        break;
+
+                    case 'center-center':
+                    case 'center-middle':
+                        $posx = $posx - round(($box[0]+$box[4])/2);
+                        $posy = $posy - round(($box[1]+$box[5])/2);
+                        break;
+
+                    case 'right-center':
+                    case 'right-middle':
+                        $posx = $posx - round(($box[2]+$box[4])/2);
+                        $posy = $posy - round(($box[3]+$box[5])/2);
+                        break;
+
+                    case 'left-center':
+                    case 'left-middle':
+                        $posx = $posx - round(($box[0]+$box[6])/2);
+                        $posy = $posy - round(($box[1]+$box[7])/2);
+                        break;
+
+                    case 'center-bottom':
+                        $posx = $posx - round(($box[0]+$box[2])/2);
+                        $posy = $posy - round(($box[1]+$box[3])/2);
+                        break;
+
+                    case 'right-bottom':
+                        $posx = $posx - $box[2];
+                        $posy = $posy - $box[3];
+                        break;
+
+                    case 'left-bottom':
+                        $posx = $posx - $box[0];
+                        $posy = $posy - $box[1];
+                        break;
+                }
+            }
+
+            // enable alphablending for imagettftext
+            imagealphablending($image->getCore(), true);
+
+            // draw ttf text
+            imagettftext($image->getCore(), $this->getPointSize(), $this->angle, $posx, $posy, $color->getInt(), $this->file, $this->text);
+
+        } else {
+
+            // get box size
+            $box = $this->getBoxSize();
+            $width = $box['width'];
+            $height = $box['height'];
+
+            // internal font specific position corrections
+            if ($this->getInternalFont() == 1) {
+                $top_correction = 1;
+                $bottom_correction = 2;
+            } elseif ($this->getInternalFont() == 3) {
+                $top_correction = 2;
+                $bottom_correction = 4;
+            } else {
+                $top_correction = 3;
+                $bottom_correction = 4;
+            }
+
+            // x-position corrections for horizontal alignment
+            switch (strtolower($this->align)) {
+                case 'center':
+                    $posx = ceil($posx - ($width / 2));
+                    break;
+
+                case 'right':
+                    $posx = ceil($posx - $width) + 1;
+                    break;
+            }
+
+            // y-position corrections for vertical alignment
+            switch (strtolower($this->valign)) {
+                case 'center':
+                case 'middle':
+                    $posy = ceil($posy - ($height / 2));
+                    break;
+
+                case 'top':
+                    $posy = ceil($posy - $top_correction);
+                    break;
+
+                default:
+                case 'bottom':
+                    $posy = round($posy - $height + $bottom_correction);
+                    break;
+            }
+
+            // draw text
+            imagestring($image->getCore(), $this->getInternalFont(), $posx, $posy, $this->text, $color->getInt());
+        }
+    }
+}

+ 40 - 0
plugins/intervention-image/Intervention/Image/Gd/Shapes/CircleShape.php

@@ -0,0 +1,40 @@
+<?php
+
+namespace Intervention\Image\Gd\Shapes;
+
+use \Intervention\Image\Image;
+
+class CircleShape extends EllipseShape
+{
+    /**
+     * Diameter of circle in pixels
+     *
+     * @var integer
+     */
+    public $diameter = 100;
+
+    /**
+     * Create new instance of circle
+     *
+     * @param integer $diameter
+     */
+    public function __construct($diameter = null)
+    {
+        $this->width = is_numeric($diameter) ? intval($diameter) : $this->diameter;
+        $this->height = is_numeric($diameter) ? intval($diameter) : $this->diameter;
+        $this->diameter = is_numeric($diameter) ? intval($diameter) : $this->diameter;
+    }
+
+    /**
+     * Draw current circle on given image
+     *
+     * @param  Image   $image
+     * @param  integer $x
+     * @param  integer $y
+     * @return boolean
+     */
+    public function applyToImage(Image $image, $x = 0, $y = 0)
+    {
+        return parent::applyToImage($image, $x, $y);
+    }
+}

+ 64 - 0
plugins/intervention-image/Intervention/Image/Gd/Shapes/EllipseShape.php

@@ -0,0 +1,64 @@
+<?php
+
+namespace Intervention\Image\Gd\Shapes;
+
+use \Intervention\Image\Image;
+use \Intervention\Image\Gd\Color;
+
+class EllipseShape extends \Intervention\Image\AbstractShape
+{
+    /**
+     * Width of ellipse in pixels
+     *
+     * @var integer
+     */
+    public $width = 100;
+
+    /**
+     * Height of ellipse in pixels
+     *
+     * @var integer
+     */
+    public $height = 100;
+
+    /**
+     * Create new ellipse instance
+     *
+     * @param integer $width
+     * @param integer $height
+     */
+    public function __construct($width = null, $height = null)
+    {
+        $this->width = is_numeric($width) ? intval($width) : $this->width;
+        $this->height = is_numeric($height) ? intval($height) : $this->height;
+    }
+
+    /**
+     * Draw ellipse instance on given image
+     *
+     * @param  Image   $image
+     * @param  integer $x
+     * @param  integer $y
+     * @return boolean
+     */
+    public function applyToImage(Image $image, $x = 0, $y = 0)
+    {
+        // parse background color
+        $background = new Color($this->background);
+
+        if ($this->hasBorder()) {
+            // slightly smaller ellipse to keep 1px bordered edges clean
+            imagefilledellipse($image->getCore(), $x, $y, $this->width-1, $this->height-1, $background->getInt());
+
+            $border_color = new Color($this->border_color);
+            imagesetthickness($image->getCore(), $this->border_width);
+
+            // gd's imageellipse doesn't respect imagesetthickness so i use imagearc with 359.9 degrees here
+            imagearc($image->getCore(), $x, $y, $this->width, $this->height, 0, 359.99, $border_color->getInt());
+        } else {
+            imagefilledellipse($image->getCore(), $x, $y, $this->width, $this->height, $background->getInt());
+        }
+
+        return true;
+    }
+}

+ 89 - 0
plugins/intervention-image/Intervention/Image/Gd/Shapes/LineShape.php

@@ -0,0 +1,89 @@
+<?php
+
+namespace Intervention\Image\Gd\Shapes;
+
+use \Intervention\Image\Image;
+use \Intervention\Image\Gd\Color;
+
+class LineShape extends \Intervention\Image\AbstractShape
+{
+    /**
+     * Starting point x-coordinate of line
+     *
+     * @var integer
+     */
+    public $x = 0;
+
+    /**
+     * Starting point y-coordinate of line
+     *
+     * @var integer
+     */
+    public $y = 0;
+
+    /**
+     * Color of line
+     *
+     * @var string
+     */
+    public $color = '#000000';
+
+    /**
+     * Width of line in pixels
+     *
+     * @var integer
+     */
+    public $width = 1;
+
+    /**
+     * Create new line shape instance
+     *
+     * @param integer $x
+     * @param integer $y
+     */
+    public function __construct($x = null, $y = null)
+    {
+        $this->x = is_numeric($x) ? intval($x) : $this->x;
+        $this->y = is_numeric($y) ? intval($y) : $this->y;
+    }
+
+    /**
+     * Set current line color
+     *
+     * @param  string $color
+     * @return void
+     */
+    public function color($color)
+    {
+        $this->color = $color;
+    }
+
+    /**
+     * Set current line width in pixels
+     *
+     * @param  integer $width
+     * @return void
+     */
+    public function width($width)
+    {
+        throw new \Intervention\Image\Exception\NotSupportedException(
+            "Line width is not supported by GD driver."
+        );
+    }
+
+    /**
+     * Draw current instance of line to given endpoint on given image
+     *
+     * @param  Image   $image
+     * @param  integer $x
+     * @param  integer $y
+     * @return boolean
+     */
+    public function applyToImage(Image $image, $x = 0, $y = 0)
+    {
+        $color = new Color($this->color);
+        imageline($image->getCore(), $x, $y, $this->x, $this->y, $color->getInt());
+
+        return true;
+    }
+}

+ 48 - 0
plugins/intervention-image/Intervention/Image/Gd/Shapes/PolygonShape.php

@@ -0,0 +1,48 @@
+<?php
+
+namespace Intervention\Image\Gd\Shapes;
+
+use \Intervention\Image\Image;
+use \Intervention\Image\Gd\Color;
+
+class PolygonShape extends \Intervention\Image\AbstractShape
+{
+    /**
+     * Array of points of polygon
+     *
+     * @var integer
+     */
+    public $points;
+
+    /**
+     * Create new polygon instance
+     *
+     * @param array $points
+     */
+    public function __construct($points)
+    {
+        $this->points = $points;
+    }
+
+    /**
+     * Draw polygon on given image
+     *
+     * @param  Image   $image
+     * @param  integer $x
+     * @param  integer $y
+     * @return boolean
+     */
+    public function applyToImage(Image $image, $x = 0, $y = 0)
+    {
+        $background = new Color($this->background);
+        imagefilledpolygon($image->getCore(), $this->points, intval(count($this->points) / 2), $background->getInt());
+        
+        if ($this->hasBorder()) {
+            $border_color = new Color($this->border_color);
+            imagesetthickness($image->getCore(), $this->border_width);
+            imagepolygon($image->getCore(), $this->points, intval(count($this->points) / 2), $border_color->getInt());
+        }
+    
+        return true;
+    }
+}

+ 75 - 0
plugins/intervention-image/Intervention/Image/Gd/Shapes/RectangleShape.php

@@ -0,0 +1,75 @@
+<?php
+
+namespace Intervention\Image\Gd\Shapes;
+
+use \Intervention\Image\Image;
+use \Intervention\Image\Gd\Color;
+
+class RectangleShape extends \Intervention\Image\AbstractShape
+{
+    /**
+     * X-Coordinate of top-left point
+     *
+     * @var integer
+     */
+    public $x1 = 0;
+
+    /**
+     * Y-Coordinate of top-left point
+     *
+     * @var integer
+     */
+    public $y1 = 0;
+
+    /**
+     * X-Coordinate of bottom-right point
+     *
+     * @var integer
+     */
+    public $x2 = 0;
+
+    /**
+     * Y-Coordinate of bottom-right point
+     *
+     * @var integer
+     */
+    public $y2 = 0;
+
+    /**
+     * Create new rectangle shape instance
+     *
+     * @param integer $x1
+     * @param integer $y1
+     * @param integer $x2
+     * @param integer $y2
+     */
+    public function __construct($x1 = null, $y1 = null, $x2 = null, $y2 = null)
+    {
+        $this->x1 = is_numeric($x1) ? intval($x1) : $this->x1;
+        $this->y1 = is_numeric($y1) ? intval($y1) : $this->y1;
+        $this->x2 = is_numeric($x2) ? intval($x2) : $this->x2;
+        $this->y2 = is_numeric($y2) ? intval($y2) : $this->y2;
+    }
+
+    /**
+     * Draw rectangle to given image at certain position
+     *
+     * @param  Image   $image
+     * @param  integer $x
+     * @param  integer $y
+     * @return boolean
+     */
+    public function applyToImage(Image $image, $x = 0, $y = 0)
+    {
+        $background = new Color($this->background);
+        imagefilledrectangle($image->getCore(), $this->x1, $this->y1, $this->x2, $this->y2, $background->getInt());
+
+        if ($this->hasBorder()) {
+            $border_color = new Color($this->border_color);
+            imagesetthickness($image->getCore(), $this->border_width);
+            imagerectangle($image->getCore(), $this->x1, $this->y1, $this->x2, $this->y2, $border_color->getInt());
+        }
+
+        return true;
+    }
+}

+ 372 - 0
plugins/intervention-image/Intervention/Image/Image.php

@@ -0,0 +1,372 @@
+<?php
+
+namespace Intervention\Image;
+
+/**
+ * @method \Intervention\Image\Image backup(string $name = 'default')                                                                                                     Backups current image state as fallback for reset method under an optional name. Overwrites older state on every call, unless a different name is passed.
+ * @method \Intervention\Image\Image blur(integer $amount = 1)                                                                                                            Apply a gaussian blur filter with a optional amount on the current image. Use values between 0 and 100.
+ * @method \Intervention\Image\Image brightness(integer $level)                                                                                                           Changes the brightness of the current image by the given level. Use values between -100 for min. brightness. 0 for no change and +100 for max. brightness.
+ * @method \Intervention\Image\Image cache(\Closure $callback, integer $lifetime = null, boolean $returnObj)                                                              Method to create a new cached image instance from a Closure callback. Pass a lifetime in minutes for the callback and decide whether you want to get an Intervention Image instance as return value or just receive the image stream.
+ * @method \Intervention\Image\Image canvas(integer $width, integer $height, mixed $bgcolor = null)                                                                       Factory method to create a new empty image instance with given width and height. You can define a background-color optionally. By default the canvas background is transparent.
+ * @method \Intervention\Image\Image circle(integer $radius, integer $x, integer $y, \Closure $callback = null)                                                           Draw a circle at given x, y, coordinates with given radius. You can define the appearance of the circle by an optional closure callback.
+ * @method \Intervention\Image\Image colorize(integer $red, integer $green, integer $blue)                                                                                Change the RGB color values of the current image on the given channels red, green and blue. The input values are normalized so you have to include parameters from 100 for maximum color value. 0 for no change and -100 to take out all the certain color on the image.
+ * @method \Intervention\Image\Image contrast(integer $level)                                                                                                             Changes the contrast of the current image by the given level. Use values between -100 for min. contrast 0 for no change and +100 for max. contrast.
+ * @method \Intervention\Image\Image crop(integer $width, integer $height, integer $x = null, integer $y = null)                                                          Cut out a rectangular part of the current image with given width and height. Define optional x,y coordinates to move the top-left corner of the cutout to a certain position.
+ * @method void                      destroy()                                                                                                                            Frees memory associated with the current image instance before the PHP script ends. Normally resources are destroyed automatically after the script is finished.
+ * @method \Intervention\Image\Image ellipse(integer $width, integer $height, integer $x, integer $y, \Closure $callback = null)                                          Draw a colored ellipse at given x, y, coordinates. You can define width and height and set the appearance of the circle by an optional closure callback.
+ * @method mixed                     exif(string $key = null)                                                                                                             Read Exif meta data from current image.
+ * @method mixed                     iptc(string $key = null)                                                                                                             Read Iptc meta data from current image.
+ * @method \Intervention\Image\Image fill(mixed $filling, integer $x = null, integer $y = null)                                                                           Fill current image with given color or another image used as tile for filling. Pass optional x, y coordinates to start at a certain point.
+ * @method \Intervention\Image\Image flip(mixed $mode = 'h')                                                                                                              Mirror the current image horizontally or vertically by specifying the mode.
+ * @method \Intervention\Image\Image fit(integer $width, integer $height = null, \Closure $callback = null, string $position = 'center')                                  Combine cropping and resizing to format image in a smart way. The method will find the best fitting aspect ratio of your given width and height on the current image automatically, cut it out and resize it to the given dimension. You may pass an optional Closure callback as third parameter, to prevent possible upsizing and a custom position of the cutout as fourth parameter.
+ * @method \Intervention\Image\Image gamma(float $correction)                                                                                                             Performs a gamma correction operation on the current image.
+ * @method \Intervention\Image\Image greyscale()                                                                                                                          Turns image into a greyscale version.
+ * @method \Intervention\Image\Image heighten(integer $height, \Closure $callback = null)                                                                                 Resizes the current image to new height, constraining aspect ratio. Pass an optional Closure callback as third parameter, to apply additional constraints like preventing possible upsizing.
+ * @method \Intervention\Image\Image insert(mixed $source, string $position = 'top-left', integer $x = 0, integer $y = 0)                                                 Paste a given image source over the current image with an optional position and a offset coordinate. This method can be used to apply another image as watermark because the transparency values are maintained.
+ * @method \Intervention\Image\Image interlace(boolean $interlace = true)                                                                                                 Determine whether an image should be encoded in interlaced or standard mode by toggling interlace mode with a boolean parameter. If an JPEG image is set interlaced the image will be processed as a progressive JPEG.
+ * @method \Intervention\Image\Image invert()                                                                                                                             Reverses all colors of the current image.
+ * @method \Intervention\Image\Image limitColors(integer $count, mixed $matte = null)                                                                                     Method converts the existing colors of the current image into a color table with a given maximum count of colors. The function preserves as much alpha channel information as possible and blends transarent pixels against a optional matte color.
+ * @method \Intervention\Image\Image line(integer $x1, integer $y1, integer $x2, integer $y2, \Closure $callback = null)                                                  Draw a line from x,y point 1 to x,y point 2 on current image. Define color and/or width of line in an optional Closure callback.
+ * @method \Intervention\Image\Image make(mixed $source)                                                                                                                  Universal factory method to create a new image instance from source, which can be a filepath, a GD image resource, an Imagick object or a binary image data.
+ * @method \Intervention\Image\Image mask(mixed $source, boolean $mask_with_alpha)                                                                                        Apply a given image source as alpha mask to the current image to change current opacity. Mask will be resized to the current image size. By default a greyscale version of the mask is converted to alpha values, but you can set mask_with_alpha to apply the actual alpha channel. Any transparency values of the current image will be maintained.
+ * @method \Intervention\Image\Image opacity(integer $transparency)                                                                                                       Set the opacity in percent of the current image ranging from 100% for opaque and 0% for full transparency.
+ * @method \Intervention\Image\Image orientate()                                                                                                                          This method reads the EXIF image profile setting 'Orientation' and performs a rotation on the image to display the image correctly.
+ * @method mixed                     pickColor(integer $x, integer $y, string $format = 'array')                                                                          Pick a color at point x, y out of current image and return in optional given format.
+ * @method \Intervention\Image\Image pixel(mixed $color, integer $x, integer $y)                                                                                          Draw a single pixel in given color on x, y position.
+ * @method \Intervention\Image\Image pixelate(integer $size)                                                                                                              Applies a pixelation effect to the current image with a given size of pixels.
+ * @method \Intervention\Image\Image polygon(array $points, \Closure $callback = null)                                                                                    Draw a colored polygon with given points. You can define the appearance of the polygon by an optional closure callback.
+ * @method \Intervention\Image\Image rectangle(integer $x1, integer $y1, integer $x2, integer $y2, \Closure $callback = null)                                             Draw a colored rectangle on current image with top-left corner on x,y point 1 and bottom-right corner at x,y point 2. Define the overall appearance of the shape by passing a Closure callback as an optional parameter.
+ * @method \Intervention\Image\Image reset(string $name = 'default')                                                                                                      Resets all of the modifications to a state saved previously by backup under an optional name.
+ * @method \Intervention\Image\Image resize(integer $width, integer $height, \Closure $callback = null)                                                                   Resizes current image based on given width and/or height. To contraint the resize command, pass an optional Closure callback as third parameter.
+ * @method \Intervention\Image\Image resizeCanvas(integer $width, integer $height, string $anchor = 'center', boolean $relative = false, mixed $bgcolor = '#000000')      Resize the boundaries of the current image to given width and height. An anchor can be defined to determine from what point of the image the resizing is going to happen. Set the mode to relative to add or subtract the given width or height to the actual image dimensions. You can also pass a background color for the emerging area of the image.
+ * @method mixed                     response(string $format = null, integer $quality = 90)                                                                               Sends HTTP response with current image in given format and quality.
+ * @method \Intervention\Image\Image rotate(float $angle, string $bgcolor = '#000000')                                                                                    Rotate the current image counter-clockwise by a given angle. Optionally define a background color for the uncovered zone after the rotation.
+ * @method \Intervention\Image\Image sharpen(integer $amount = 10)                                                                                                        Sharpen current image with an optional amount. Use values between 0 and 100.
+ * @method \Intervention\Image\Image text(string $text, integer $x = 0, integer $y = 0, \Closure $callback = null)                                                        Write a text string to the current image at an optional x,y basepoint position. You can define more details like font-size, font-file and alignment via a callback as the fourth parameter.
+ * @method \Intervention\Image\Image trim(string $base = 'top-left', array $away = array('top', 'bottom', 'left', 'right'), integer $tolerance = 0, integer $feather = 0) Trim away image space in given color. Define an optional base to pick a color at a certain position and borders that should be trimmed away. You can also set an optional tolerance level, to trim similar colors and add a feathering border around the trimed image.
+ * @method \Intervention\Image\Image widen(integer $width, \Closure $callback = null)                                                                                     Resizes the current image to new width, constraining aspect ratio. Pass an optional Closure callback as third parameter, to apply additional constraints like preventing possible upsizing.
+ */
+class Image extends File
+{
+    /**
+     * Instance of current image driver
+     *
+     * @var AbstractDriver
+     */
+    protected $driver;
+
+    /**
+     * Image resource/object of current image processor
+     *
+     * @var mixed
+     */
+    protected $core;
+
+    /**
+     * Array of Image resource backups of current image processor
+     *
+     * @var array
+     */
+    protected $backups = array();
+
+    /**
+     * Last image encoding result
+     *
+     * @var string
+     */
+    public $encoded = '';
+
+    /**
+     * Creates a new Image instance
+     *
+     * @param AbstractDriver $driver
+     * @param mixed  $core
+     */
+    public function __construct(AbstractDriver $driver = null, $core = null)
+    {
+        $this->driver = $driver;
+        $this->core = $core;
+    }
+
+    /**
+     * Magic method to catch all image calls
+     * usually any AbstractCommand
+     *
+     * @param  string $name
+     * @param  Array  $arguments
+     * @return mixed
+     */
+    public function __call($name, $arguments)
+    {
+        $command = $this->driver->executeCommand($this, $name, $arguments);
+        return $command->hasOutput() ? $command->getOutput() : $this;
+    }
+
+    /**
+     * Starts encoding of current image
+     *
+     * @param  string  $format
+     * @param  integer $quality
+     * @return \Intervention\Image\Image
+     */
+    public function encode($format = null, $quality = 90)
+    {
+        return $this->driver->encode($this, $format, $quality);
+    }
+
+    /**
+     * Saves encoded image in filesystem
+     *
+     * @param  string  $path
+     * @param  integer $quality
+     * @return \Intervention\Image\Image
+     */
+    public function save($path = null, $quality = null)
+    {
+        $path = is_null($path) ? $this->basePath() : $path;
+
+        if (is_null($path)) {
+            throw new Exception\NotWritableException(
+                "Can't write to undefined path."
+            );
+        }
+
+        $data = $this->encode(pathinfo($path, PATHINFO_EXTENSION), $quality);
+        $saved = @file_put_contents($path, $data);
+
+        if ($saved === false) {
+            throw new Exception\NotWritableException(
+                "Can't write image data to path ({$path})"
+            );
+        }
+
+        // set new file info
+        $this->setFileInfoFromPath($path);
+
+        return $this;
+    }
+
+    /**
+     * Runs a given filter on current image
+     *
+     * @param  FiltersFilterInterface $filter
+     * @return \Intervention\Image\Image
+     */
+    public function filter(Filters\FilterInterface $filter)
+    {
+        return $filter->applyFilter($this);
+    }
+
+    /**
+     * Returns current image driver
+     *
+     * @return \Intervention\Image\AbstractDriver
+     */
+    public function getDriver()
+    {
+        return $this->driver;
+    }
+
+    /**
+     * Sets current image driver
+     * @param AbstractDriver $driver
+     */
+    public function setDriver(AbstractDriver $driver)
+    {
+        $this->driver = $driver;
+
+        return $this;
+    }
+
+    /**
+     * Returns current image resource/obj
+     *
+     * @return mixed
+     */
+    public function getCore()
+    {
+        return $this->core;
+    }
+
+    /**
+     * Sets current image resource
+     *
+     * @param mixed $core
+     */
+    public function setCore($core)
+    {
+        $this->core = $core;
+
+        return $this;
+    }
+
+    /**
+     * Returns current image backup
+     *
+     * @param string $name
+     * @return mixed
+     */
+    public function getBackup($name = null)
+    {
+        $name = is_null($name) ? 'default' : $name;
+
+        if ( ! $this->backupExists($name)) {
+            throw new \Intervention\Image\Exception\RuntimeException(
+                "Backup with name ({$name}) not available. Call backup() before reset()."
+            );
+        }
+
+        return $this->backups[$name];
+    }
+
+    /**
+     * Returns all backups attached to image
+     *
+     * @return array
+     */
+    public function getBackups()
+    {
+        return $this->backups;
+    }
+
+    /**
+     * Sets current image backup
+     *
+     * @param mixed  $resource
+     * @param string $name
+     * @return self
+     */
+    public function setBackup($resource, $name = null)
+    {
+        $name = is_null($name) ? 'default' : $name;
+
+        $this->backups[$name] = $resource;
+
+        return $this;
+    }
+
+    /**
+     * Checks if named backup exists
+     *
+     * @param  string $name
+     * @return bool
+     */
+    private function backupExists($name)
+    {
+        return array_key_exists($name, $this->backups);
+    }
+
+    /**
+     * Checks if current image is already encoded
+     *
+     * @return boolean
+     */
+    public function isEncoded()
+    {
+        return ! is_null($this->encoded);
+    }
+
+    /**
+     * Returns encoded image data of current image
+     *
+     * @return string
+     */
+    public function getEncoded()
+    {
+        return $this->encoded;
+    }
+
+    /**
+     * Sets encoded image buffer
+     *
+     * @param string $value
+     */
+    public function setEncoded($value)
+    {
+        $this->encoded = $value;
+
+        return $this;
+    }
+
+    /**
+     * Calculates current image width
+     *
+     * @return integer
+     */
+    public function getWidth()
+    {
+        return $this->getSize()->width;
+    }
+
+    /**
+     * Alias of getWidth()
+     *
+     * @return integer
+     */
+    public function width()
+    {
+        return $this->getWidth();
+    }
+
+    /**
+     * Calculates current image height
+     *
+     * @return integer
+     */
+    public function getHeight()
+    {
+        return $this->getSize()->height;
+    }
+
+    /**
+     * Alias of getHeight
+     *
+     * @return integer
+     */
+    public function height()
+    {
+        return $this->getHeight();
+    }
+
+    /**
+     * Reads mime type
+     *
+     * @return string
+     */
+    public function mime()
+    {
+        return $this->mime;
+    }
+
+    /**
+     * Get fully qualified path to image
+     *
+     * @return string
+     */
+    public function basePath()
+    {
+        if ($this->dirname && $this->basename) {
+            return ($this->dirname .'/'. $this->basename);
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns encoded image data in string conversion
+     *
+     * @return string
+     */
+    public function __toString()
+    {
+        return $this->encoded;
+    }
+
+    /**
+     * Cloning an image
+     */
+    public function __clone()
+    {
+        $this->core = $this->driver->cloneCore($this->core);
+    }
+}

+ 128 - 0
plugins/intervention-image/Intervention/Image/ImageManager.php

@@ -0,0 +1,128 @@
+<?php
+
+namespace Intervention\Image;
+
+use Closure;
+
+class ImageManager
+{
+    /**
+     * Config
+     *
+     * @var array
+     */
+    public $config = array(
+        'driver' => 'gd'
+    );
+
+    /**
+     * Creates new instance of Image Manager
+     *
+     * @param array $config
+     */
+    public function __construct(array $config = array())
+    {
+        $this->checkRequirements();
+        $this->configure($config);
+    }
+
+    /**
+     * Overrides configuration settings
+     *
+     * @param array $config
+     */
+    public function configure(array $config = array())
+    {
+        $this->config = array_replace($this->config, $config);
+
+        return $this;
+    }
+
+    /**
+     * Initiates an Image instance from different input types
+     *
+     * @param  mixed $data
+     *
+     * @return \Intervention\Image\Image
+     */
+    public function make($data)
+    {
+        return $this->createDriver()->init($data);
+    }
+
+    /**
+     * Creates an empty image canvas
+     *
+     * @param  integer $width
+     * @param  integer $height
+     * @param  mixed $background
+     *
+     * @return \Intervention\Image\Image
+     */
+    public function canvas($width, $height, $background = null)
+    {
+        return $this->createDriver()->newImage($width, $height, $background);
+    }
+
+    /**
+     * Create new cached image and run callback
+     * (requires additional package intervention/imagecache)
+     *
+     * @param Closure $callback
+     * @param integer $lifetime
+     * @param boolean $returnObj
+     *
+     * @return Image
+     */
+    public function cache(Closure $callback, $lifetime = null, $returnObj = false)
+    {
+        if (class_exists('Intervention\\Image\\ImageCache')) {
+            // create imagecache
+            $imagecache = new ImageCache($this);
+
+            // run callback
+            if (is_callable($callback)) {
+                $callback($imagecache);
+            }
+
+            return $imagecache->get($lifetime, $returnObj);
+        }
+
+        throw new \Intervention\Image\Exception\MissingDependencyException(
+            "Please install package intervention/imagecache before running this function."
+        );
+    }
+
+    /**
+     * Creates a driver instance according to config settings
+     *
+     * @return \Intervention\Image\AbstractDriver
+     */
+    private function createDriver()
+    {
+        $drivername = ucfirst($this->config['driver']);
+        $driverclass = sprintf('Intervention\\Image\\%s\\Driver', $drivername);
+
+        if (class_exists($driverclass)) {
+            return new $driverclass;
+        }
+
+        throw new \Intervention\Image\Exception\NotSupportedException(
+            "Driver ({$drivername}) could not be instantiated."
+        );
+    }
+
+    /**
+     * Check if all requirements are available
+     *
+     * @return void
+     */
+    private function checkRequirements()
+    {
+        if ( ! function_exists('finfo_buffer')) {
+            throw new \Intervention\Image\Exception\MissingDependencyException(
+                "PHP Fileinfo extension must be installed/enabled to use Intervention Image."
+            );
+        }
+    }
+}

+ 87 - 0
plugins/intervention-image/Intervention/Image/ImageManagerStatic.php

@@ -0,0 +1,87 @@
+<?php
+
+namespace Intervention\Image;
+
+use Closure;
+
+class ImageManagerStatic
+{
+    /**
+     * Instance of Intervention\Image\ImageManager
+     *
+     * @var ImageManager
+     */
+    public static $manager;
+
+    /**
+     * Creates a new instance
+     *
+     * @param ImageManager $manager
+     */
+    public function __construct(ImageManager $manager = null)
+    {
+        self::$manager = $manager ? $manager : new ImageManager;
+    }
+
+    /**
+     * Get or create new ImageManager instance
+     *
+     * @return ImageManager
+     */
+    public static function getManager()
+    {
+        return self::$manager ? self::$manager : new ImageManager;
+    }
+
+    /**
+     * Statically create new custom configured image manager
+     *
+     * @param  array $config
+     *
+     * @return ImageManager
+     */
+    public static function configure(array $config = array())
+    {
+        return self::$manager = self::getManager()->configure($config);
+    }
+
+    /**
+     * Statically initiates an Image instance from different input types
+     *
+     * @param  mixed $data
+     *
+     * @return \Intervention\Image\Image
+     */
+    public static function make($data)
+    {
+        return self::getManager()->make($data);
+    }
+
+    /**
+     * Statically creates an empty image canvas
+     *
+     * @param  integer $width
+     * @param  integer $height
+     * @param  mixed $background
+     *
+     * @return \Intervention\Image\Image
+     */
+    public static function canvas($width, $height, $background = null)
+    {
+        return self::getManager()->canvas($width, $height, $background);
+    }
+
+    /**
+     * Create new cached image and run callback statically
+     *
+     * @param  Closure  $callback
+     * @param  integer  $lifetime
+     * @param  boolean  $returnObj
+     *
+     * @return mixed
+     */
+    public static function cache(Closure $callback, $lifetime = null, $returnObj = false)
+    {
+        return self::getManager()->cache($callback, $lifetime, $returnObj);
+    }
+}

+ 81 - 0
plugins/intervention-image/Intervention/Image/ImageServiceProvider.php

@@ -0,0 +1,81 @@
+<?php
+
+namespace Intervention\Image;
+
+use Illuminate\Support\ServiceProvider;
+
+class ImageServiceProvider extends ServiceProvider
+{
+    /**
+     * Indicates if loading of the provider is deferred.
+     *
+     * @var bool
+     */
+    protected $defer = false;
+
+    /**
+     * Actual provider
+     *
+     * @var \Illuminate\Support\ServiceProvider
+     */
+    protected $provider;
+
+    /**
+     * Create a new service provider instance.
+     *
+     * @param  \Illuminate\Contracts\Foundation\Application  $app
+     * @return void
+     */
+    public function __construct($app)
+    {
+        parent::__construct($app);
+
+        $this->provider = $this->getProvider();
+    }
+
+    /**
+     * Bootstrap the application events.
+     *
+     * @return void
+     */
+    public function boot()
+    {
+        return $this->provider->boot();
+    }
+
+    /**
+     * Register the service provider.
+     *
+     * @return void
+     */
+    public function register()
+    {
+        return $this->provider->register();
+    }
+
+    /**
+     * Return ServiceProvider according to Laravel version
+     *
+     * @return \Intervention\Image\Provider\ProviderInterface
+     */
+    private function getProvider()
+    {
+        $app = $this->app;
+        $version = intval($app::VERSION);
+        $provider = sprintf(
+            '\Intervention\Image\ImageServiceProviderLaravel%d', $version
+        );
+
+        return new $provider($app);
+    }
+
+    /**
+     * Get the services provided by the provider.
+     *
+     * @return array
+     */
+    public function provides()
+    {
+        return array('image');
+    }
+}

+ 100 - 0
plugins/intervention-image/Intervention/Image/ImageServiceProviderLaravel4.php

@@ -0,0 +1,100 @@
+<?php
+
+namespace Intervention\Image;
+
+use Illuminate\Support\ServiceProvider;
+use Illuminate\Http\Response as IlluminateResponse;
+
+class ImageServiceProviderLaravel4 extends ServiceProvider
+{
+    /**
+     * Bootstrap the application events.
+     *
+     * @return void
+     */
+    public function boot()
+    {
+        $this->package('intervention/image');
+
+        // try to create imagecache route only if imagecache is present
+        if (class_exists('Intervention\\Image\\ImageCache')) {
+
+            $app = $this->app;
+
+            // load imagecache config
+            $app['config']->package('intervention/imagecache', __DIR__.'/../../../../imagecache/src/config', 'imagecache');
+            $config = $app['config'];
+
+            // create dynamic manipulation route
+            if (is_string($config->get('imagecache::route'))) {
+
+                // add original to route templates
+                $config->set('imagecache::templates.original', null);
+
+                // setup image manipulator route
+                $app['router']->get($config->get('imagecache::route').'/{template}/{filename}', array('as' => 'imagecache', function ($template, $filename) use ($app, $config) {
+
+                    // disable session cookies for image route
+                    $app['config']->set('session.driver', 'array');
+
+                    // find file
+                    foreach ($config->get('imagecache::paths') as $path) {
+                        // don't allow '..' in filenames
+                        $image_path = $path.'/'.str_replace('..', '', $filename);
+                        if (file_exists($image_path) && is_file($image_path)) {
+                            break;
+                        } else {
+                            $image_path = false;
+                        }
+                    }
+
+                    // abort if file not found
+                    if ($image_path === false) {
+                        $app->abort(404);
+                    }
+
+                    // define template callback
+                    $callback = $config->get("imagecache::templates.{$template}");
+
+                    if (is_callable($callback)) {
+
+                        // image manipulation based on callback
+                        $content = $app['image']->cache(function ($image) use ($image_path, $callback) {
+                            return $callback($image->make($image_path));
+                        }, $config->get('imagecache::lifetime'));
+
+                    } else {
+
+                        // get original image file contents
+                        $content = file_get_contents($image_path);
+                    }
+
+                    // define mime type
+                    $mime = finfo_buffer(finfo_open(FILEINFO_MIME_TYPE), $content);
+
+                    // return http response
+                    return new IlluminateResponse($content, 200, array(
+                        'Content-Type' => $mime,
+                        'Cache-Control' => 'max-age='.($config->get('imagecache::lifetime')*60).', public',
+                        'Etag' => md5($content)
+                    ));
+
+                }))->where(array('template' => join('|', array_keys($config->get('imagecache::templates'))), 'filename' => '[ \w\\.\\/\\-]+'));
+            }
+        }
+    }
+
+    /**
+     * Register the service provider.
+     *
+     * @return void
+     */
+    public function register()
+    {
+        $app = $this->app;
+
+        $app['image'] = $app->share(function ($app) {
+            return new ImageManager($app['config']->get('image::config'));
+        });
+    }
+}

+ 135 - 0
plugins/intervention-image/Intervention/Image/ImageServiceProviderLaravel5.php

@@ -0,0 +1,135 @@
+<?php
+
+namespace Intervention\Image;
+
+use Illuminate\Support\ServiceProvider;
+use Illuminate\Http\Response as IlluminateResponse;
+
+class ImageServiceProviderLaravel5 extends ServiceProvider
+{
+    /**
+     * Determines if Intervention Imagecache is installed
+     *
+     * @return boolean
+     */
+    private function cacheIsInstalled()
+    {
+        return class_exists('Intervention\\Image\\ImageCache');
+    }
+
+    /**
+     * Bootstrap the application events.
+     *
+     * @return void
+     */
+    public function boot()
+    {
+        $this->publishes(array(
+            __DIR__.'/../../config/config.php' => config_path('image.php')
+        ));
+
+        // setup intervention/imagecache if package is installed
+        $this->cacheIsInstalled() ? $this->bootstrapImageCache() : null;
+    }
+
+    /**
+     * Register the service provider.
+     *
+     * @return void
+     */
+    public function register()
+    {
+        $app = $this->app;
+
+        // merge default config
+        $this->mergeConfigFrom(
+            __DIR__.'/../../config/config.php',
+            'image'
+        );
+
+        // create image
+        $app['image'] = $app->share(function ($app) {
+            return new ImageManager($app['config']->get('image'));
+        });
+    }
+
+    /**
+     * Bootstrap imagecache
+     *
+     * @return void
+     */
+    private function bootstrapImageCache()
+    {
+        $app = $this->app;
+        $config = __DIR__.'/../../../../imagecache/src/config/config.php';
+
+        $this->publishes(array(
+            $config => config_path('imagecache.php')
+        ));
+
+        // merge default config
+        $this->mergeConfigFrom(
+            $config,
+            'imagecache'
+        );
+
+        $config = $app['config'];
+
+        // create dynamic manipulation route
+        if (is_string($config->get('imagecache.route'))) {
+
+            // add original to route templates
+            $config->set('imagecache.templates.original', null);
+
+            $app['router']->get($config->get('imagecache.route').'/{template}/{filename}', array('as' => 'imagecache', function ($template, $filename) use ($app, $config) {
+
+                
+                // disable session cookies for image route
+                $app['config']->set('session.driver', 'array');
+
+                // find file
+                foreach ($config->get('imagecache.paths') as $path) {
+                    // don't allow '..' in filenames
+                    $image_path = $path.'/'.str_replace('..', '', $filename);
+                    if (file_exists($image_path) && is_file($image_path)) {
+                        break;
+                    } else {
+                        $image_path = false;
+                    }
+                }
+
+                // abort if file not found
+                if ($image_path === false) {
+                    $app->abort(404);
+                }
+
+                // define template callback
+                $callback = $config->get("imagecache.templates.{$template}");
+
+                if (is_callable($callback)) {
+
+                    // image manipulation based on callback
+                    $content = $app['image']->cache(function ($image) use ($image_path, $callback) {
+                        return $callback($image->make($image_path));
+                    }, $config->get('imagecache.lifetime'));
+
+                } else {
+
+                    // get original image file contents
+                    $content = file_get_contents($image_path);
+                }
+
+                // define mime type
+                $mime = finfo_buffer(finfo_open(FILEINFO_MIME_TYPE), $content);
+
+                // return http response
+                return new IlluminateResponse($content, 200, array(
+                    'Content-Type' => $mime,
+                    'Cache-Control' => 'max-age='.($config->get('imagecache.lifetime')*60).', public',
+                    'Etag' => md5($content)
+                ));
+
+            }))->where(array('template' => join('|', array_keys($config->get('imagecache.templates'))), 'filename' => '[ \w\\.\\/\\-]+'));
+        }
+    }
+}

+ 277 - 0
plugins/intervention-image/Intervention/Image/Imagick/Color.php

@@ -0,0 +1,277 @@
+<?php
+
+namespace Intervention\Image\Imagick;
+
+class Color extends \Intervention\Image\AbstractColor
+{
+    /**
+     * ImagickPixel containing current color information
+     *
+     * @var \ImagickPixel
+     */
+    public $pixel;
+
+    /**
+     * Initiates color object from integer
+     *
+     * @param  integer $value
+     * @return \Intervention\Image\AbstractColor
+     */
+    public function initFromInteger($value)
+    {
+        $a = ($value >> 24) & 0xFF;
+        $r = ($value >> 16) & 0xFF;
+        $g = ($value >> 8) & 0xFF;
+        $b = $value & 0xFF;
+        $a = $this->rgb2alpha($a);
+
+        $this->setPixel($r, $g, $b, $a);
+    }
+
+    /**
+     * Initiates color object from given array
+     *
+     * @param  array $value
+     * @return \Intervention\Image\AbstractColor
+     */
+    public function initFromArray($array)
+    {
+        $array = array_values($array);
+
+        if (count($array) == 4) {
+
+            // color array with alpha value
+            list($r, $g, $b, $a) = $array;
+
+        } elseif (count($array) == 3) {
+
+            // color array without alpha value
+            list($r, $g, $b) = $array;
+            $a = 1;
+        }
+
+        $this->setPixel($r, $g, $b, $a);
+    }
+
+    /**
+     * Initiates color object from given string
+     *
+     * @param  string $value
+     *
+     * @return \Intervention\Image\AbstractColor
+     */
+    public function initFromString($value)
+    {
+        if ($color = $this->rgbaFromString($value)) {
+            $this->setPixel($color[0], $color[1], $color[2], $color[3]);
+        }
+    }
+
+    /**
+     * Initiates color object from given ImagickPixel object
+     *
+     * @param  ImagickPixel $value
+     *
+     * @return \Intervention\Image\AbstractColor
+     */
+    public function initFromObject($value)
+    {
+        if (is_a($value, '\ImagickPixel')) {
+            $this->pixel = $value;
+        }
+    }
+
+    /**
+     * Initiates color object from given R, G and B values
+     *
+     * @param  integer $r
+     * @param  integer $g
+     * @param  integer $b
+     *
+     * @return \Intervention\Image\AbstractColor
+     */
+    public function initFromRgb($r, $g, $b)
+    {
+        $this->setPixel($r, $g, $b);
+    }
+
+    /**
+     * Initiates color object from given R, G, B and A values
+     *
+     * @param  integer $r
+     * @param  integer $g
+     * @param  integer $b
+     * @param  float   $a
+     *
+     * @return \Intervention\Image\AbstractColor
+     */
+    public function initFromRgba($r, $g, $b, $a)
+    {
+        $this->setPixel($r, $g, $b, $a);
+    }
+
+    /**
+     * Calculates integer value of current color instance
+     *
+     * @return integer
+     */
+    public function getInt()
+    {
+        $r = $this->getRedValue();
+        $g = $this->getGreenValue();
+        $b = $this->getBlueValue();
+        $a = intval(round($this->getAlphaValue() * 255));
+
+        return intval(($a << 24) + ($r << 16) + ($g << 8) + $b);
+    }
+
+    /**
+     * Calculates hexadecimal value of current color instance
+     *
+     * @param  string $prefix
+     *
+     * @return string
+     */
+    public function getHex($prefix = '')
+    {
+        return sprintf('%s%02x%02x%02x', $prefix,
+            $this->getRedValue(),
+            $this->getGreenValue(),
+            $this->getBlueValue()
+        );
+    }
+
+    /**
+     * Calculates RGB(A) in array format of current color instance
+     *
+     * @return array
+     */
+    public function getArray()
+    {
+        return array(
+            $this->getRedValue(),
+            $this->getGreenValue(),
+            $this->getBlueValue(),
+            $this->getAlphaValue()
+        );
+    }
+
+    /**
+     * Calculates RGBA in string format of current color instance
+     *
+     * @return string
+     */
+    public function getRgba()
+    {
+        return sprintf('rgba(%d, %d, %d, %.2f)',
+            $this->getRedValue(),
+            $this->getGreenValue(),
+            $this->getBlueValue(),
+            $this->getAlphaValue()
+        );
+    }
+
+    /**
+     * Determines if current color is different from given color
+     *
+     * @param  AbstractColor $color
+     * @param  integer       $tolerance
+     * @return boolean
+     */
+    public function differs(\Intervention\Image\AbstractColor $color, $tolerance = 0)
+    {
+        $color_tolerance = round($tolerance * 2.55);
+        $alpha_tolerance = round($tolerance);
+
+        $delta = array(
+            'r' => abs($color->getRedValue() - $this->getRedValue()),
+            'g' => abs($color->getGreenValue() - $this->getGreenValue()),
+            'b' => abs($color->getBlueValue() - $this->getBlueValue()),
+            'a' => abs($color->getAlphaValue() - $this->getAlphaValue())
+        );
+
+        return (
+            $delta['r'] > $color_tolerance or
+            $delta['g'] > $color_tolerance or
+            $delta['b'] > $color_tolerance or
+            $delta['a'] > $alpha_tolerance
+        );
+    }
+
+    /**
+     * Returns RGB red value of current color
+     *
+     * @return integer
+     */
+    public function getRedValue()
+    {
+        return intval(round($this->pixel->getColorValue(\Imagick::COLOR_RED) * 255));
+    }
+
+    /**
+     * Returns RGB green value of current color
+     *
+     * @return integer
+     */
+    public function getGreenValue()
+    {
+        return intval(round($this->pixel->getColorValue(\Imagick::COLOR_GREEN) * 255));
+    }
+
+    /**
+     * Returns RGB blue value of current color
+     *
+     * @return integer
+     */
+    public function getBlueValue()
+    {
+        return intval(round($this->pixel->getColorValue(\Imagick::COLOR_BLUE) * 255));
+    }
+
+    /**
+     * Returns RGB alpha value of current color
+     *
+     * @return float
+     */
+    public function getAlphaValue()
+    {
+        return round($this->pixel->getColorValue(\Imagick::COLOR_ALPHA), 2);
+    }
+
+    /**
+     * Initiates ImagickPixel from given RGBA values
+     *
+     * @return \ImagickPixel
+     */
+    private function setPixel($r, $g, $b, $a = null)
+    {
+        $a = is_null($a) ? 1 : $a;
+
+        return $this->pixel = new \ImagickPixel(
+            sprintf('rgba(%d, %d, %d, %.2f)', $r, $g, $b, $a)
+        );
+    }
+
+    /**
+     * Returns current color as ImagickPixel
+     *
+     * @return \ImagickPixel
+     */
+    public function getPixel()
+    {
+        return $this->pixel;
+    }
+
+    /**
+     * Calculates RGA integer alpha value into float value
+     *
+     * @param  integer $value
+     * @return float
+     */
+    private function rgb2alpha($value)
+    {
+        // (255 -> 1.0) / (0 -> 0.0)
+        return (float) round($value/255, 2);
+    }
+
+}

+ 22 - 0
plugins/intervention-image/Intervention/Image/Imagick/Commands/BackupCommand.php

@@ -0,0 +1,22 @@
+<?php
+
+namespace Intervention\Image\Imagick\Commands;
+
+class BackupCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Saves a backups of current state of image core
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $backupName = $this->argument(0)->value();
+
+        // clone current image resource
+        $image->setBackup(clone $image->getCore(), $backupName);
+
+        return true;
+    }
+}

+ 19 - 0
plugins/intervention-image/Intervention/Image/Imagick/Commands/BlurCommand.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace Intervention\Image\Imagick\Commands;
+
+class BlurCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Applies blur effect on image
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $amount = $this->argument(0)->between(0, 100)->value(1);
+
+        return $image->getCore()->blurImage(1 * $amount, 0.5 * $amount);
+    }
+}

+ 19 - 0
plugins/intervention-image/Intervention/Image/Imagick/Commands/BrightnessCommand.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace Intervention\Image\Imagick\Commands;
+
+class BrightnessCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Changes image brightness
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $level = $this->argument(0)->between(-100, 100)->required()->value();
+
+        return $image->getCore()->modulateImage(100 + $level, 100, 100);
+    }
+}

+ 42 - 0
plugins/intervention-image/Intervention/Image/Imagick/Commands/ColorizeCommand.php

@@ -0,0 +1,42 @@
+<?php
+
+namespace Intervention\Image\Imagick\Commands;
+
+class ColorizeCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Changes balance of different RGB color channels
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $red = $this->argument(0)->between(-100, 100)->required()->value();
+        $green = $this->argument(1)->between(-100, 100)->required()->value();
+        $blue = $this->argument(2)->between(-100, 100)->required()->value();
+
+        // normalize colorize levels
+        $red = $this->normalizeLevel($red);
+        $green = $this->normalizeLevel($green);
+        $blue = $this->normalizeLevel($blue);
+
+        $qrange = $image->getCore()->getQuantumRange();
+
+        // apply
+        $image->getCore()->levelImage(0, $red, $qrange['quantumRangeLong'], \Imagick::CHANNEL_RED);
+        $image->getCore()->levelImage(0, $green, $qrange['quantumRangeLong'], \Imagick::CHANNEL_GREEN);
+        $image->getCore()->levelImage(0, $blue, $qrange['quantumRangeLong'], \Imagick::CHANNEL_BLUE);
+
+        return true;
+    }
+
+    private function normalizeLevel($level)
+    {
+        if ($level > 0) {
+            return $level/5;
+        } else {
+            return ($level+100)/100;
+        }
+    }
+}

+ 19 - 0
plugins/intervention-image/Intervention/Image/Imagick/Commands/ContrastCommand.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace Intervention\Image\Imagick\Commands;
+
+class ContrastCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Changes contrast of image
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $level = $this->argument(0)->between(-100, 100)->required()->value();
+
+        return $image->getCore()->sigmoidalContrastImage($level > 0, $level / 4, 0);
+    }
+}

+ 43 - 0
plugins/intervention-image/Intervention/Image/Imagick/Commands/CropCommand.php

@@ -0,0 +1,43 @@
+<?php
+
+namespace Intervention\Image\Imagick\Commands;
+
+use \Intervention\Image\Point;
+use \Intervention\Image\Size;
+
+class CropCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Crop an image instance
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $width = $this->argument(0)->type('digit')->required()->value();
+        $height = $this->argument(1)->type('digit')->required()->value();
+        $x = $this->argument(2)->type('digit')->value();
+        $y = $this->argument(3)->type('digit')->value();
+
+        if (is_null($width) || is_null($height)) {
+            throw new \Intervention\Image\Exception\InvalidArgumentException(
+                "Width and height of cutout needs to be defined."
+            );
+        }
+
+        $cropped = new Size($width, $height);
+        $position = new Point($x, $y);
+
+        // align boxes
+        if (is_null($x) && is_null($y)) {
+            $position = $image->getSize()->align('center')->relativePosition($cropped->align('center'));
+        }
+
+        // crop image core
+        $image->getCore()->cropImage($cropped->width, $cropped->height, $position->x, $position->y);
+        $image->getCore()->setImagePage(0,0,0,0);
+
+        return true;
+    }
+}

+ 25 - 0
plugins/intervention-image/Intervention/Image/Imagick/Commands/DestroyCommand.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace Intervention\Image\Imagick\Commands;
+
+class DestroyCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Destroys current image core and frees up memory
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        // destroy image core
+        $image->getCore()->clear();
+
+        // destroy backups    
+        foreach ($image->getBackups() as $backup) {
+            $backup->clear();
+        }
+
+        return true;
+    }
+}

+ 103 - 0
plugins/intervention-image/Intervention/Image/Imagick/Commands/FillCommand.php

@@ -0,0 +1,103 @@
+<?php
+
+namespace Intervention\Image\Imagick\Commands;
+
+use \Intervention\Image\Image;
+use \Intervention\Image\Imagick\Decoder;
+use \Intervention\Image\Imagick\Color;
+
+class FillCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Fills image with color or pattern
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $filling = $this->argument(0)->value();
+        $x = $this->argument(1)->type('digit')->value();
+        $y = $this->argument(2)->type('digit')->value();
+
+        $imagick = $image->getCore();
+
+        try {
+            // set image filling
+            $source = new Decoder;
+            $filling = $source->init($filling);
+
+        } catch (\Intervention\Image\Exception\NotReadableException $e) {
+
+            // set solid color filling
+            $filling = new Color($filling);
+        }
+
+        // flood fill if coordinates are set
+        if (is_int($x) && is_int($y)) {
+
+            // flood fill with texture
+            if ($filling instanceof Image) {
+
+                // create tile
+                $tile = clone $image->getCore();
+
+                // mask away color at position
+                $tile->transparentPaintImage($tile->getImagePixelColor($x, $y), 0, 0, false);
+
+                // create canvas
+                $canvas = clone $image->getCore();
+
+                // fill canvas with texture
+                $canvas = $canvas->textureImage($filling->getCore());
+
+                // merge canvas and tile
+                $canvas->compositeImage($tile, \Imagick::COMPOSITE_DEFAULT, 0, 0);
+
+                // replace image core
+                $image->setCore($canvas);
+
+            // flood fill with color
+            } elseif ($filling instanceof Color) {
+
+                // create canvas with filling
+                $canvas = new \Imagick;
+                $canvas->newImage($image->getWidth(), $image->getHeight(), $filling->getPixel(), 'png');
+
+                // create tile to put on top
+                $tile = clone $image->getCore();
+
+                // mask away color at pos.
+                $tile->transparentPaintImage($tile->getImagePixelColor($x, $y), 0, 0, false);
+
+                // save alpha channel of original image
+                $alpha = clone $image->getCore();
+
+                // merge original with canvas and tile
+                $image->getCore()->compositeImage($canvas, \Imagick::COMPOSITE_DEFAULT, 0, 0);
+                $image->getCore()->compositeImage($tile, \Imagick::COMPOSITE_DEFAULT, 0, 0);
+
+                // restore alpha channel of original image
+                $image->getCore()->compositeImage($alpha, \Imagick::COMPOSITE_COPYOPACITY, 0, 0);
+            }
+
+        } else {
+
+            if ($filling instanceof Image) {
+
+                // fill whole image with texture
+                $image->setCore($image->getCore()->textureImage($filling->getCore()));
+
+            } elseif ($filling instanceof Color) {
+
+                // fill whole image with color
+                $draw = new \ImagickDraw();
+                $draw->setFillColor($filling->getPixel());
+                $draw->rectangle(0, 0, $image->getWidth(), $image->getHeight());
+                $image->getCore()->drawImage($draw);
+            }
+        }
+
+        return true;
+    }
+}

+ 41 - 0
plugins/intervention-image/Intervention/Image/Imagick/Commands/FitCommand.php

@@ -0,0 +1,41 @@
+<?php
+
+namespace Intervention\Image\Imagick\Commands;
+
+use \Intervention\Image\Size;
+
+class FitCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Crops and resized an image at the same time
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $width = $this->argument(0)->type('digit')->required()->value();
+        $height = $this->argument(1)->type('digit')->value($width);
+        $constraints = $this->argument(2)->type('closure')->value();
+        $position = $this->argument(3)->type('string')->value('center');
+
+        // calculate size
+        $cropped = $image->getSize()->fit(new Size($width, $height), $position);
+        $resized = clone $cropped;
+        $resized = $resized->resize($width, $height, $constraints);
+
+        // crop image
+        $image->getCore()->cropImage(
+            $cropped->width,
+            $cropped->height,
+            $cropped->pivot->x,
+            $cropped->pivot->y
+        );
+
+        // resize image
+        $image->getCore()->resizeImage($resized->getWidth(), $resized->getHeight(), \Imagick::FILTER_BOX, 1);
+        $image->getCore()->setImagePage(0,0,0,0);
+
+        return true;
+    }
+}

+ 25 - 0
plugins/intervention-image/Intervention/Image/Imagick/Commands/FlipCommand.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace Intervention\Image\Imagick\Commands;
+
+class FlipCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Mirrors an image
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $mode = $this->argument(0)->value('h');
+
+        if (in_array(strtolower($mode), array(2, 'v', 'vert', 'vertical'))) {
+            // flip vertical
+            return $image->getCore()->flipImage();
+        } else {
+            // flip horizontal
+            return $image->getCore()->flopImage();
+        }
+    }
+}

+ 19 - 0
plugins/intervention-image/Intervention/Image/Imagick/Commands/GammaCommand.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace Intervention\Image\Imagick\Commands;
+
+class GammaCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Applies gamma correction to a given image
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $gamma = $this->argument(0)->type('numeric')->required()->value();
+
+        return $image->getCore()->gammaImage($gamma);
+    }
+}

+ 26 - 0
plugins/intervention-image/Intervention/Image/Imagick/Commands/GetSizeCommand.php

@@ -0,0 +1,26 @@
+<?php
+
+namespace Intervention\Image\Imagick\Commands;
+
+use \Intervention\Image\Size;
+
+class GetSizeCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Reads size of given image instance in pixels
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $info = $image->getCore()->identifyImage();
+
+        $this->setOutput(new Size(
+            $info['geometry']['width'],
+            $info['geometry']['height']
+        ));
+
+        return true;
+    }
+}

+ 17 - 0
plugins/intervention-image/Intervention/Image/Imagick/Commands/GreyscaleCommand.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace Intervention\Image\Imagick\Commands;
+
+class GreyscaleCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Turns an image into a greyscale version
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        return $image->getCore()->modulateImage(100, 0, 100);
+    }
+}

+ 28 - 0
plugins/intervention-image/Intervention/Image/Imagick/Commands/HeightenCommand.php

@@ -0,0 +1,28 @@
+<?php
+
+namespace Intervention\Image\Imagick\Commands;
+
+class HeightenCommand extends ResizeCommand
+{
+    /**
+     * Resize image proportionally to given height
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $height = $this->argument(0)->type('digit')->required()->value();
+        $additionalConstraints = $this->argument(1)->type('closure')->value();
+
+        $this->arguments[0] = null;
+        $this->arguments[1] = $height;
+        $this->arguments[2] = function ($constraint) use ($additionalConstraints) {
+            $constraint->aspectRatio();
+            if(is_callable($additionalConstraints)) 
+                $additionalConstraints($constraint);
+        };
+
+        return parent::execute($image);
+    }
+}

+ 31 - 0
plugins/intervention-image/Intervention/Image/Imagick/Commands/InsertCommand.php

@@ -0,0 +1,31 @@
+<?php
+
+namespace Intervention\Image\Imagick\Commands;
+
+class InsertCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Insert another image into given image
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $source = $this->argument(0)->required()->value();
+        $position = $this->argument(1)->type('string')->value();
+        $x = $this->argument(2)->type('digit')->value(0);
+        $y = $this->argument(3)->type('digit')->value(0);
+
+        // build watermark
+        $watermark = $image->getDriver()->init($source);
+
+        // define insertion point
+        $image_size = $image->getSize()->align($position, $x, $y);
+        $watermark_size = $watermark->getSize()->align($position);
+        $target = $image_size->relativePosition($watermark_size);
+
+        // insert image at position
+        return $image->getCore()->compositeImage($watermark->getCore(), \Imagick::COMPOSITE_DEFAULT, $target->x, $target->y);
+    }
+}

+ 27 - 0
plugins/intervention-image/Intervention/Image/Imagick/Commands/InterlaceCommand.php

@@ -0,0 +1,27 @@
+<?php
+
+namespace Intervention\Image\Imagick\Commands;
+
+class InterlaceCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Toggles interlaced encoding mode
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $mode = $this->argument(0)->type('bool')->value(true);
+
+        if ($mode) {
+            $mode = \Imagick::INTERLACE_LINE;
+        } else {
+            $mode = \Imagick::INTERLACE_NO;
+        }
+
+        $image->getCore()->setInterlaceScheme($mode);
+
+        return true;
+    }
+}

+ 17 - 0
plugins/intervention-image/Intervention/Image/Imagick/Commands/InvertCommand.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace Intervention\Image\Imagick\Commands;
+
+class InvertCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Inverts colors of an image
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        return $image->getCore()->negateImage(false);
+    }
+}

+ 57 - 0
plugins/intervention-image/Intervention/Image/Imagick/Commands/LimitColorsCommand.php

@@ -0,0 +1,57 @@
+<?php
+
+namespace Intervention\Image\Imagick\Commands;
+
+class LimitColorsCommand extends \Intervention\Image\Commands\AbstractCommand
+{
+    /**
+     * Reduces colors of a given image
+     *
+     * @param  \Intervention\Image\Image $image
+     * @return boolean
+     */
+    public function execute($image)
+    {
+        $count = $this->argument(0)->value();
+        $matte = $this->argument(1)->value();
+
+        // get current image size
+        $size = $image->getSize();
+
+        // build 2 color alpha mask from original alpha
+        $alpha = clone $image->getCore();
+        $alpha->separateImageChannel(\Imagick::CHANNEL_ALPHA);
+        $alpha->transparentPaintImage('#ffffff', 0, 0, false);
+        $alpha->separateImageChannel(\Imagick::CHANNEL_ALPHA);
+        $alpha->negateImage(false);
+
+        if ($matte) {
+
+            // get matte color
+            $mattecolor = $image->getDriver()->parseColor($matte)->getPixel();
+
+            // create matte image
+            $canvas = new \Imagick;
+            $canvas->newImage($size->width, $size->height, $mattecolor, 'png');
+
+            // lower colors of original and copy to matte
+            $image->getCore()->quantizeImage($count, \Imagick::COLORSPACE_RGB, 0, false, false);
+            $canvas->compositeImage($image->getCore(), \Imagick::COMPOSITE_DEFAULT, 0, 0);
+
+            // copy new alpha to canvas
+            $canvas->compositeImage($alpha, \Imagick::COMPOSITE_COPYOPACITY, 0, 0);
+
+            // replace core
+            $image->setCore($canvas);
+
+        } else {
+
+            $image->getCore()->quantizeImage($count, \Imagick::COLORSPACE_RGB, 0, false, false);
+            $image->getCore()->compositeImage($alpha, \Imagick::COMPOSITE_COPYOPACITY, 0, 0);
+
+        }
+
+        return true;
+
+    }
+}

Некоторые файлы не были показаны из-за большого количества измененных файлов