From e4ff269a600d81848ff3b93ebf327576287c4218 Mon Sep 17 00:00:00 2001 From: Mikael Roos Date: Wed, 9 May 2012 17:57:48 +0200 Subject: [PATCH] implemented filters and quality, chenged how arguments was handled. --- CImage.php | 138 +++++++++++++++++++++++++++++++++++++++++++++-------- README.md | 14 ++++++ img.php | 33 ++++++++----- test.php | 77 +++++++++++++++++++++++------- 4 files changed, 213 insertions(+), 49 deletions(-) diff --git a/CImage.php b/CImage.php index e203bcb..4ffade1 100644 --- a/CImage.php +++ b/CImage.php @@ -19,7 +19,12 @@ class CImage { private $cropWidth; private $cropHeight; public $keepRatio; + public $cropToFit; public $crop; + public $crop_x; + public $crop_y; + private $quality; + public $filters; public $saveFolder; public $newName; private $newFileName; @@ -35,20 +40,12 @@ class CImage { * Constructor, can take arguments to init the object. * * @param $pathToImage string the filepath to the image. - * @param $newWidth integer the new width or null. - * @param $newHeight integer the new width or null. - * @param $keepRatio boolean true to keep aspect ratio else false. * @param $saveFolder string path to folder where to save the new file or null to skip saving. * @param $newName string new filename or leave to null to autogenerate filename. */ - public function __construct($pathToImage=null, $newWidth=null, $newHeight=null, - $keepRatio=true, $crop=false, $saveFolder=null, $newName=null) { + public function __construct($pathToImage=null, $saveFolder=null, $newName=null) { $this->pathToImage = $pathToImage; $this->fileExtension = pathinfo($this->pathToImage, PATHINFO_EXTENSION); - $this->newWidth = $newWidth; - $this->newHeight = $newHeight; - $this->keepRatio = $keepRatio; - $this->crop = $crop; $this->saveFolder = $saveFolder; $this->newName = $newName; } @@ -69,8 +66,20 @@ class CImage { */ public function CreateFilename() { $parts = pathinfo($this->pathToImage); - $crop = $this->crop ? '_c_' : null; - return $this->saveFolder . '/' . $parts['filename'] . '_' . round($this->newWidth) . '_' . round($this->newHeight) . $crop . '.' . $parts['extension']; + $crop = $this->cropToFit ? '_cf' : null; + $crop_x = $this->crop_x ? "_x{$this->crop_x}" : null; + $crop_y = $this->crop_y ? "_y{$this->crop_y}" : null; + $quality = $this->quality == 100 ? null : "_q{$this->quality}"; + $filters = null; + foreach($this->filters as $filter) { + if(is_array($filter)) { + $filters .= "_f{$filter['id']}"; + for($i=1;$i<=$filter['argc'];$i++) { + $filters .= ":".$filter["arg{$i}"]; + } + } + } + return $this->saveFolder . '/' . $parts['filename'] . '_' . round($this->newWidth) . '_' . round($this->newHeight) . $crop . $crop_x . $crop_y . $quality . $filters . '.' . $parts['extension']; } @@ -80,6 +89,9 @@ class CImage { public function Init() { is_null($this->newWidth) or is_numeric($this->newWidth) or $this->RaiseError('Width not numeric'); is_null($this->newHeight) or is_numeric($this->newHeight) or $this->RaiseError('Height not numeric'); + is_numeric($this->quality) and $this->quality >= 0 and $this->quality <= 100 or $this->RaiseError('Quality not in range.'); + //is_numeric($this->crop_x) && is_numeric($this->crop_y) or $this->RaiseError('Quality not in range.'); + //filter is_readable($this->pathToImage) or $this->RaiseError('File does not exist.'); in_array($this->fileExtension, $this->validExtensions) or $this->RaiseError('Not a valid file extension.'); is_null($this->saveFolder) or is_writable($this->saveFolder) or $this->RaiseError('Save directory does not exist or is not writable.'); @@ -126,6 +138,34 @@ class CImage { } + /** + * Map filter name to PHP filter and id. + * + * @param string $name the name of the filter. + */ + private function MapFilter($name) { + $map = array( + 'negate' => array('id'=>0, 'argc'=>0, 'type'=>IMG_FILTER_NEGATE), + 'grayscale' => array('id'=>1, 'argc'=>0, 'type'=>IMG_FILTER_GRAYSCALE), + 'brightness' => array('id'=>2, 'argc'=>1, 'type'=>IMG_FILTER_BRIGHTNESS), + 'contrast' => array('id'=>3, 'argc'=>1, 'type'=>IMG_FILTER_CONTRAST), + 'colorize' => array('id'=>4, 'argc'=>4, 'type'=>IMG_FILTER_COLORIZE), + 'edgedetect' => array('id'=>5, 'argc'=>0, 'type'=>IMG_FILTER_EDGEDETECT), + 'emboss' => array('id'=>6, 'argc'=>0, 'type'=>IMG_FILTER_EMBOSS), + 'gaussian_blur' => array('id'=>7, 'argc'=>0, 'type'=>IMG_FILTER_GAUSSIAN_BLUR), + 'selective_blur' => array('id'=>8, 'argc'=>0, 'type'=>IMG_FILTER_SELECTIVE_BLUR), + 'mean_removal' => array('id'=>9, 'argc'=>0, 'type'=>IMG_FILTER_MEAN_REMOVAL), + 'smooth' => array('id'=>10, 'argc'=>1, 'type'=>IMG_FILTER_SMOOTH), + 'pixelate' => array('id'=>11, 'argc'=>2, 'type'=>IMG_FILTER_PIXELATE), + ); + if(isset($map[$name])) + return $map[$name]; + else { + $this->RaiseError('No such filter.'); + } + } + + /** * Calculate new width and height of image. */ @@ -137,7 +177,7 @@ class CImage { if(isset($this->newWidth) && isset($this->newHeight)) { // Use newWidth and newHeigh as min width/height, image should fit the area. - if($this->crop) { + if($this->cropToFit) { $ratioWidth = $this->width/$this->newWidth; $ratioHeight = $this->height/$this->newHeight; $ratio = ($ratioWidth < $ratioHeight) ? $ratioWidth : $ratioHeight; @@ -179,8 +219,55 @@ class CImage { /** * Resize the image and optionally store/cache the new imagefile. Output the image. + * + * @param integer $newWidth the new width or null. Default is null. + * @param integer $newHeight the new width or null. Default is null. + * @param boolean $keepRatio true to keep aspect ratio else false. Default is true. + * @param boolean $cropToFit true to crop image to fit in box specified by $newWidth and $newHeight. Default is false. + * @param integer $quality the quality to use when saving the file, range 0-100, default is full quality which is 100. + * @param array $crop. + * @param array $filter. */ - public function ResizeAndOutput() { + public function ResizeAndOutput($args) { + $defaults = array( + 'newWidth'=>null, + 'newHeight'=>null, + 'keepRatio'=>true, + 'cropToFit'=>false, + 'quality'=>100, + 'crop'=>array('w'=>null, 'h'=>null, 'x'=>0, 'y'=>0), + 'filters'=>null, + ); + // Convert crop settins from string to array + if(isset($args['crop']) && !is_array($args['crop'])) { + $args['crop'] = array(); + } + + // Convert filter settins from array of string to array of array + if(isset($args['filters']) && is_array($args['filters'])) { + foreach($args['filters'] as $key => $filterStr) { + $parts = explode(',', $filterStr); + $filter = $this->MapFilter($parts[0]); + $filter['str'] = $filterStr; + for($i=1;$i<=$filter['argc'];$i++) { + if(isset($parts[$i])) { + $filter["arg{$i}"] = $parts[$i]; + } else { + $this->RaiseError('Missing arg to filter, review how many arguments are needed at http://php.net/manual/en/function.imagefilter.php'); + } + } + $args['filters'][$key] = $filter; + } + } + //echo "
" . print_r($args['filters'], true) . "
"; + + // Merge default arguments with incoming and set properties. + $args = array_merge($defaults, $args); + foreach($defaults as $key=>$val) { + $this->{$key} = $args[$key]; + } + + // Init the object and do sanity checks on arguments $this->Init()->CalculateNewWidthAndHeight(); // Use original image? @@ -188,8 +275,6 @@ class CImage { $this->Output($this->pathToImage); } - //echo "{$this->newWidth}:{$this->newHeight}"; - // Check cache before resizing. $this->newFileName = $this->CreateFilename(); if(is_readable($this->newFileName)) { @@ -208,10 +293,9 @@ class CImage { /** * Resize, crop and output the image. * - * @param $imageQuality number the quality to use when saving the file, default is full quality. */ - public function ResizeAndSave($imageQuality="100") { - if($this->crop) { + public function ResizeAndSave() { + if($this->cropToFit) { $cropX = ($this->cropWidth/2) - ($this->newWidth/2); $cropY = ($this->cropHeight/2) - ($this->newHeight/2); $imgPreCrop = imagecreatetruecolor($this->cropWidth, $this->cropHeight); @@ -222,14 +306,26 @@ class CImage { $imageResized = imagecreatetruecolor($this->newWidth, $this->newHeight); imagecopyresampled($imageResized, $this->image, 0, 0, 0, 0, $this->newWidth, $this->newHeight, $this->width, $this->height); } - + + if(isset($this->filters) && is_array($this->filters)) { + foreach($this->filters as $filter) { + switch($filter['argc']) { + case 0: imagefilter($imageResized, $filter['type']); break; + case 1: imagefilter($imageResized, $filter['type'], $filter['arg1']); break; + case 2: imagefilter($imageResized, $filter['type'], $filter['arg1'], $filter['arg2']); break; + case 3: imagefilter($imageResized, $filter['type'], $filter['arg1'], $filter['arg2'], $filter['arg3']); break; + case 4: imagefilter($imageResized, $filter['type'], $filter['arg1'], $filter['arg2'], $filter['arg3'], $filter['arg4']); break; + } + } + } + switch($this->fileExtension) { case 'jpg': case 'jpeg': if(imagetypes() & IMG_JPG) { if($this->saveFolder) { - imagejpeg($imageResized, $this->newFileName, $imageQuality); + imagejpeg($imageResized, $this->newFileName, $this->quality); } $imgFunction = 'imagejpeg'; } @@ -249,7 +345,7 @@ class CImage { $quality = 9 - round(($imageQuality/100) * 9); if (imagetypes() & IMG_PNG) { if($this->saveFolder) { - imagepng($imageResized, $this->newFileName, $quality); + imagepng($imageResized, $this->newFileName, $this->quality); } $imgFunction = 'imagepng'; } diff --git a/README.md b/README.md index bc67aad..0bcbad3 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,20 @@ Mikael Roos (mos@dbwebb.se) Revision history ---------------- +ToDo. + +* crop +* Pre-defined sizes. + + +v0.2 (2012-05-09) + +* Implemented filters as in http://php.net/manual/en/function.imagefilter.php +* Changed `crop` to `crop_to_fit`, woks the same way. +* Changed arguments to method and sends them in array. +* Added quality-setting. +* Added testcases for above. + v0.1.1 (2012-04-27) * Corrected calculation where both width and height were set. diff --git a/img.php b/img.php index 966e47e..d10c388 100644 --- a/img.php +++ b/img.php @@ -14,21 +14,30 @@ $pathToCache = __DIR__.'/cache/'; $srcImage = isset($_GET['src']) ? $pathToImages . basename($_GET['src']) : null; $newWidth = isset($_GET['width']) ? $_GET['width'] : (isset($_GET['w']) ? $_GET['w'] : null); $newHeight = isset($_GET['height']) ? $_GET['height'] : (isset($_GET['h']) ? $_GET['h'] : null); -$keepRatio = isset($_GET['no-ratio']) ? false : true; // Keep Aspect Ratio? -$crop = isset($_GET['crop']) ? true : false; // Crop image? +$keepRatio = isset($_GET['no-ratio']) ? false : true; +$cropToFit = isset($_GET['crop-to-fit']) ? true : false; +$crop = isset($_GET['crop']) ? $_GET['crop'] : (isset($_GET['c']) ? $_GET['c'] : null); +$quality = isset($_GET['quality']) ? $_GET['quality'] : (isset($_GET['q']) ? $_GET['q'] : 100); + +// Add all filters to an array +$filters = array(); +$filter = isset($_GET['filter']) ? $_GET['filter'] : (isset($_GET['f']) ? $_GET['f'] : null); +if($filter) { $filters[] = $filter; } +for($i=0; $i<10;$i++) { + $filter = isset($_GET["filter{$i}"]) ? $_GET["filter{$i}"] : (isset($_GET["f{$i}"]) ? $_GET["f{$i}"] : null); + if($filter) { $filters[] = $filter; } +} // Do some sanity checks !preg_match('/^[\w-\.]+$/', $srcImage) or die('Filename contains invalid characters.'); -if(isset($newWidth)) { - $newWidth < 1000 or die('To large width.'); - $newWidth > 10 or die('To small width.'); -} -if(isset($newHeight)) { - $newHeight < 1000 or die('To large height.'); - $newHeight > 10 or die('To small height.'); -} +is_null($newWidth) or ($newWidth > 10 && $newWidth < 1000) or die('Width out of range.'); +is_null($newHeight) or ($newHeight > 10 && $newHeight < 1000) or die('Hight out of range.'); +$quality >= 0 and $quality <= 100 or die('Quality out of range'); // Create the image object require(__DIR__.'/CImage.php'); -$img = new CImage($srcImage, $newWidth, $newHeight, $keepRatio, $crop, $pathToCache); -$img->ResizeAndOutput(); \ No newline at end of file +$img = new CImage($srcImage, $pathToCache); +$img->ResizeAndOutput(array('newWidth'=>$newWidth, 'newHeight'=>$newHeight, 'keepRatio'=>$keepRatio, + 'cropToFit'=>$cropToFit, 'quality'=>$quality, + 'crop'=>$crop, 'filters'=>$filters, + )); \ No newline at end of file diff --git a/test.php b/test.php index 709af05..cf1caca 100644 --- a/test.php +++ b/test.php @@ -7,36 +7,81 @@

Testing CImage.php through img.php

You can test any variation of resizing the images through img.php?src=wider.jpg&w=200&h=200 or img.php?src=higher.jpg&w=200&h=200

Supported arguments throught the querystring are:

+ + +

Supports .jpg, .png and .gif.

Sourcecode and issues on github: https://github.com/mosbth/cimage

Mikael Roos (mos@dbwebb.se)

+

Testcases

+ +'Original image', 'query'=>''), + array('text'=>'Max width 200.', 'query'=>'&w=200'), + array('text'=>'Max height 200.', 'query'=>'&h=200'), + array('text'=>'Max width 200 and max height 200.', 'query'=>'&w=200&h=200'), + array('text'=>'No-ratio makes image fit in area of width 200 and height 200.', 'query'=>'&w=200&h=200&no-ratio'), + array('text'=>'Crop to fit in width 200 and height 200.', 'query'=>'&w=200&h=200&crop-to-fit'), + array('text'=>'Crop to fit in width 200 and height 100.', 'query'=>'&w=200&h=100&crop-to-fit'), + array('text'=>'Crop to fit in width 100 and height 200.', 'query'=>'&w=100&h=200&crop-to-fit'), + array('text'=>'Quality 70', 'query'=>'&w=200&h=200&quality=70'), + array('text'=>'Quality 40', 'query'=>'&w=200&h=200&quality=40'), + array('text'=>'Quality 10', 'query'=>'&w=200&h=200&quality=10'), + array('text'=>'Filter: Negate', 'query'=>'&w=200&h=200&f=negate'), + array('text'=>'Filter: Grayscale', 'query'=>'&w=200&h=200&f=grayscale'), + array('text'=>'Filter: Brightness 90', 'query'=>'&w=200&h=200&f=brightness,90'), + array('text'=>'Filter: Contrast 50', 'query'=>'&w=200&h=200&f=contrast,50'), + array('text'=>'Filter: Colorize 0,255,0,0', 'query'=>'&w=200&h=200&f=colorize,0,255,0,0'), + array('text'=>'Filter: Edge detect', 'query'=>'&w=200&h=200&f=edgedetect'), + array('text'=>'Filter: Emboss', 'query'=>'&w=200&h=200&f=emboss'), + array('text'=>'Filter: Gaussian blur', 'query'=>'&w=200&h=200&f=gaussian_blur'), + array('text'=>'Filter: Selective blur', 'query'=>'&w=200&h=200&f=selective_blur'), + array('text'=>'Filter: Mean removal', 'query'=>'&w=200&h=200&f=mean_removal'), + array('text'=>'Filter: Smooth 2', 'query'=>'&w=200&h=200&f=smooth,2'), + array('text'=>'Filter: Pixelate 10,10', 'query'=>'&w=200&h=200&f=pixelate,10,10'), + array('text'=>'Multiple filter: Negate, Grayscale and Pixelate 10,10', 'query'=>'&w=200&h=200&&f=negate&f0=grayscale&f1=pixelate,10,10'), +); +?> + +

Test case with image wider.jpg

- + - - - - - - - - - - - - - - + $val) { + $url = "img.php?src=wider.jpg{$val['query']}"; + echo ""; +} +?>
Test casesTest case with image wider.jpg
Testcase:Result:
Original image of wider.jpg.
wider.jpg max width 200.
wider.jpg max height 200.
wider.jpg max width 200 and max height 200.
wider.jpg max width 200 and max height 200 and no-ratio.
wider.jpg max width 200 and max height 200 and cropped.
wider.jpg max width 200 and max height 100 and cropped.
Original image of higher.jpg.
higher.jpg max width 200.
higher.jpg max height 200.
higher.jpg max width 200 and max height 200.
higher.jpg max width 200 and max height 200 and no-ratio.
higher.jpg max width 200 and max height 200 and cropped.
higher.jpg max width 200 and max height 100 and cropped.
$key
{$val['text']}
".htmlentities($url)."
+

Test case with image higher.jpg

+ + + + + $val) { + $url = "img.php?src=higher.jpg{$val['query']}"; + echo ""; +} +?> + +
Test case with image higher.jpg
Testcase:Result:
$key
{$val['text']}
".htmlentities($url)."
+ + \ No newline at end of file