diff --git a/phpunit.xml b/phpunit.xml index a53abea3..f2b8428c 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -2,7 +2,7 @@ modify(0, 0, $pos_x , $pos_y, $width, $height, $width, $height); @@ -630,7 +638,9 @@ class Image $height = is_null($height) ? $width : $height; } else { // width or height not defined (resume with original values) - throw new Exception\ImageDimensionException('width or height needs to be defined'); + throw new Exception\DimensionOutOfBoundsException( + 'width or height needs to be defined' + ); } // ausschnitt berechnen @@ -796,12 +806,15 @@ class Image */ public function opacity($transparency) { - if ($transparency >= 0 && $transparency <= 100) { + if (is_numeric($transparency) && $transparency >= 0 && $transparency <= 100) { $transparency = intval($transparency) / 100; } else { - throw new Exception\ImageOpacityException('Opacity must be between 0 and 100'); + throw new Exception\OpacityOutOfBoundsException('Opacity must be between 0 and 100'); } + // -------------------------------------------------------------------- + // http://stackoverflow.com/questions/2396415/what-does-new-self-mean-in-php + // -------------------------------------------------------------------- // create alpha mask $alpha = new self(null, $this->width, $this->height); $alpha->fill(sprintf('rgba(0, 0, 0, %.1f)', $transparency)); @@ -1044,18 +1057,18 @@ class Image * Changes the brightness of the current image * * @param int $level [description] + * * @return Image */ public function brightness($level) { - // normalize level - if ($level >= -100 && $level <= 100) { - $level = $level * 2.55; - } else { - throw new Exception\ImageBrightnessException('Brightness level must be between -100 and +100'); + if ($level < -100 || $level > 100) { + throw new Exception\BrightnessOutOfBoundsException( + 'Brightness level must be between -100 and +100' + ); } - - imagefilter($this->resource, IMG_FILTER_BRIGHTNESS, $level); + + imagefilter($this->resource, IMG_FILTER_BRIGHTNESS, ($level * 2.55)); return $this; } @@ -1064,18 +1077,18 @@ class Image * Changes the contrast of the current image * * @param int $level + * * @return Image */ public function contrast($level) { - // normalize level - if ($level >= -100 && $level <= 100) { - $level = $level * (-1); - } else { - throw new Exception\ImageConstractException('Contrast level must be between -100 and +100'); + if ($level < -100 || $level > 100) { + throw new Exception\ContrastOutOfBoundsException( + 'Contrast level must be between -100 and +100' + ); } - imagefilter($this->resource, IMG_FILTER_CONTRAST, $level); + imagefilter($this->resource, IMG_FILTER_CONTRAST, ($level * -1)); return $this; } @@ -1387,12 +1400,19 @@ class Image /** * Read Exif data from the current 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 string $key * @return mixed */ public function exif($key = null) { + if (!function_exists('exif_read_data')) { + throw new Exception\ExifFunctionsNotAvailableException; + } + $data = exif_read_data($this->dirname .'/'. $this->basename, 'EXIF', false); if ( ! is_null($key)) { @@ -1450,6 +1470,10 @@ class Image */ private function isBinary($input) { + if (is_resource($input)) { + return false; + } + return ( ! ctype_print($input)); } @@ -1461,7 +1485,29 @@ class Image */ private function isImageResource($input) { - return (is_resource($input) && get_resource_type($input) == 'gd'); + // -------------------------------------------------------------------- + // There was a logical error in the program that wasn't considered. + // Namely, how should the program handle a valid resource handle + // that wasn't a valid image resource handle. + // + // Previously this method simply returned false if $input wasn't + // a valid image resource handle. But in the constructor, the next + // conditional passed the file handle to the Image::isBinary method. + // The Image::isBinary method only checked if the input didn't contain + // any printable characters which for a handle is true. So the program + // incorrectly assumed the file handle as a binary string causing errors. + // By throwing an exception for resource handles that are not of type + // 'gd', the program stop the futher processing of data, and the + // developer is given a descriptive exception. + // -------------------------------------------------------------------- + + if (is_resource($input)) { + if (get_resource_type($input) != 'gd') { + throw new Exception\InvalidImageResourceException; + } + return true; + } + return false; } /** @@ -1506,7 +1552,14 @@ class Image */ private function setImageInfoFromPath($path) { - $info = getimagesize($path); + $info = @getimagesize($path); + + if ($info === false) { + throw new Exception\InvalidImageTypeException( + "Wrong image type ({$this->type}) only use JPG, PNG or GIF images." + ); + } + $this->width = $info[0]; $this->height = $info[1]; $this->type = $info[2]; @@ -1530,7 +1583,9 @@ class Image break; default: - throw new Exception\InvalidImageTypeException("Wrong image type ({$this->type}) only use JPG, PNG or GIF images."); + throw new Exception\InvalidImageTypeException( + "Wrong image type ({$this->type}) only use JPG, PNG or GIF images." + ); break; } } @@ -1556,7 +1611,17 @@ class Image */ private function setImageInfoFromString($string) { - $this->resource = imagecreatefromstring($string); + // Without the '@' passing in an invalid binary-safe string will + // cause PHP to raise a warning. (Which is fine in production since + // you should have display errors off. right?) + // So supress the warning, and then check if there was an error. + $resource = @imagecreatefromstring($string); + + if ($resource === false) { + throw new Exception\InvalidImageDataStringException; + } + + $this->resource = $resource; $this->width = imagesx($this->resource); $this->height = imagesy($this->resource); $this->original['width'] = $this->width; diff --git a/tests/ImageTest.php b/tests/ImageTest.php index 195f1329..b6e80b70 100644 --- a/tests/ImageTest.php +++ b/tests/ImageTest.php @@ -56,6 +56,22 @@ class ImageTest extends PHPUnit_Framework_Testcase $this->assertEquals($img->mime, 'image/jpeg'); } + /** + * @expectedException Intervention\Image\Exception\ImageNotFoundException + */ + public function testConstructorWithInvalidPath() + { + $img = new Image('public/foo/bar/invalid_image_path.jpg'); + } + + /** + * @expectedException Intervention\Image\Exception\InvalidImageTypeException + */ + public function testContructorWithPathInvalidType() + { + $img = new Image('public/text.txt'); + } + public function testConstructoWithString() { $data = file_get_contents('public/test.jpg'); @@ -68,6 +84,17 @@ class ImageTest extends PHPUnit_Framework_Testcase $this->assertEquals($img->height, 600); } + /** + * @expectedException Intervention\Image\Exception\InvalidImageDataStringException + */ + public function testConstructionWithInvalidString() + { + // the semi-random string is base64_decoded to allow it to + // pass the isBinary conditional. + $data = base64_decode('6KKjdeyUAhRPNzxeYybZ'); + $img = new Image($data); + } + public function testConstructorWithResource() { $resource = imagecreatefromjpeg('public/test.jpg'); @@ -80,6 +107,15 @@ class ImageTest extends PHPUnit_Framework_Testcase $this->assertEquals($img->height, 600); } + /** + * @expectedException Intervention\Image\Exception\InvalidImageResourceException + */ + public function testConstructorWithInvalidResource() + { + $resource = fopen('public/test.jpg', 'r+'); + $img = new Image($resource); + } + public function testConstructorCanvas() { $img = new Image(null, 800, 600); @@ -285,6 +321,15 @@ class ImageTest extends PHPUnit_Framework_Testcase $this->assertEquals($img->height, $original_height); } + /** + * @expectedException Intervention\Image\Exception\DimensionOutOfBoundsException + */ + public function testResizeImageWithoutDimensions() + { + $img = $this->getTestImage(); + $img->resize(); + } + public function testWidenImage() { $img = $this->getTestImage(); @@ -497,6 +542,24 @@ class ImageTest extends PHPUnit_Framework_Testcase $this->assertEquals('#ffa600', $img->pickColor(99, 99, 'hex')); } + /** + * @expectedException Intervention\Image\Exception\DimensionOutOfBoundsException + */ + public function testCropImageWithoutDimensions() + { + $img = $this->getTestImage(); + $img->crop(null, null); + } + + /** + * @expectedException Intervention\Image\Exception\DimensionOutOfBoundsException + */ + public function testCropImageWithNonNumericDimensions() + { + $img = $this->getTestImage(); + $img->crop('a', 'z'); + } + public function testLegacyResize() { // auto height @@ -564,6 +627,15 @@ class ImageTest extends PHPUnit_Framework_Testcase $this->assertEquals($img->pickColor(95, 20, 'hex'), '#ffe8bf'); } + /** + * @expectedException Intervention\Image\Exception\DimensionOutOfBoundsException + */ + public function testGrabImageWithoutDimensions() + { + $img = $this->getTestImage(); + $img->grab(); + } + public function testFlipImage() { $img = $this->getTestImage(); @@ -889,6 +961,33 @@ class ImageTest extends PHPUnit_Framework_Testcase $this->assertEquals($checkColor['a'], 0.5); } + /** + * @expectedException Intervention\Image\Exception\OpacityOutOfBoundsException + */ + public function testOpacityTooHigh() + { + $img = $this->getTestImage(); + $img->opacity(101); + } + + /** + * @expectedException Intervention\Image\Exception\OpacityOutOfBoundsException + */ + public function testOpacityTooLow() + { + $img = $this->getTestImage(); + $img->opacity(-1); + } + + /** + * @expectedException Intervention\Image\Exception\OpacityOutOfBoundsException + */ + public function testOpacityAlphaChar() + { + $img = $this->getTestImage(); + $img->opacity('a'); + } + public function testMaskImage() { // simple image mask @@ -1396,6 +1495,24 @@ class ImageTest extends PHPUnit_Framework_Testcase $this->assertInternalType('int', $color); } + /** + * @expectedException Intervention\Image\Exception\ImageColorException + */ + public function testParseColorInvalidRGBColor() + { + $img = $this->getTestImage(); + $img->parseColor('rgb()'); + } + + /** + * @expectedException Intervention\Image\Exception\ImageColorException + */ + public function testParseColorInvalidHexColor() + { + $img = $this->getTestImage(); + $img->parseColor('ab'); + } + public function testBrightnessImage() { $img = $this->getTestImage(); @@ -1404,6 +1521,24 @@ class ImageTest extends PHPUnit_Framework_Testcase $this->assertInstanceOf('Intervention\Image\Image', $img); } + /** + * @expectedException Intervention\Image\Exception\BrightnessOutOfBoundsException + */ + public function testBrightnessOutOfBoundsHigh() + { + $img = $this->getTestImage(); + $img->brightness(101); + } + + /** + * @expectedException Intervention\Image\Exception\BrightnessOutOfBoundsException + */ + public function testBrightnessOutOfBoundsLow() + { + $img = $this->getTestImage(); + $img->brightness(-101); + } + public function testContrastImage() { $img = $this->getTestImage(); @@ -1412,6 +1547,24 @@ class ImageTest extends PHPUnit_Framework_Testcase $this->assertInstanceOf('Intervention\Image\Image', $img); } + /** + * @expectedException Intervention\Image\Exception\ContrastOutOfBoundsException + */ + public function testContrastOutOfBoundsHigh() + { + $img = $this->getTestImage(); + $img->contrast(101); + } + + /** + * @expectedException Intervention\Image\Exception\ContrastOutOfBoundsException + */ + public function testContrastOutOfBoundsLow() + { + $img = $this->getTestImage(); + $img->contrast(-101); + } + public function testEncode() { // default encoding