diff --git a/CImage.php b/CImage.php index eb014d5..09c812d 100644 --- a/CImage.php +++ b/CImage.php @@ -339,7 +339,8 @@ class CImage /** * Used with option area to set which parts of the image to use. */ - private $offset; + private $area; + private $offset; @@ -440,11 +441,12 @@ class CImage { $this->setSource($imageSrc, $imageFolder); $this->setTarget($saveFolder, $saveName); - $this->imageResizer = new CImageResizer(); + $this->imageResizer = new CImageResizer(array($this, 'log')); } + /** * Set verbose mode. * @@ -931,7 +933,7 @@ class CImage $this->imageResizer->setSource($this->width, $this->height); if ($this->verbose) { - $this->log("Loading image details for: {$file}"); + $this->log("#Loading image details for: {$file}"); $this->log(" Image width x height (type): {$this->width} x {$this->height} ({$this->fileType})."); $this->log(" Image filesize: " . filesize($file) . " bytes."); $this->log(" Image mimetype: " . image_type_to_mime_type($this->fileType)); @@ -951,66 +953,10 @@ class CImage */ public function initDimensions() { - $this->log("Init dimension (before) newWidth x newHeight is {$this->newWidth} x {$this->newHeight}."); - - // width as % - if ($this->newWidth[strlen($this->newWidth)-1] == '%') { - $this->newWidth = $this->width * substr($this->newWidth, 0, -1) / 100; - $this->log("Setting new width based on % to {$this->newWidth}"); - } - - // height as % - if ($this->newHeight[strlen($this->newHeight)-1] == '%') { - $this->newHeight = $this->height * substr($this->newHeight, 0, -1) / 100; - $this->log("Setting new height based on % to {$this->newHeight}"); - } - - is_null($this->aspectRatio) or is_numeric($this->aspectRatio) or $this->raiseError('Aspect ratio out of range'); - - // width & height from aspect ratio - if ($this->aspectRatio && is_null($this->newWidth) && is_null($this->newHeight)) { - if ($this->aspectRatio >= 1) { - $this->newWidth = $this->width; - $this->newHeight = $this->width / $this->aspectRatio; - $this->log("Setting new width & height based on width & aspect ratio (>=1) to (w x h) {$this->newWidth} x {$this->newHeight}"); - - } else { - $this->newHeight = $this->height; - $this->newWidth = $this->height * $this->aspectRatio; - $this->log("Setting new width & height based on width & aspect ratio (<1) to (w x h) {$this->newWidth} x {$this->newHeight}"); - } - - } elseif ($this->aspectRatio && is_null($this->newWidth)) { - $this->newWidth = $this->newHeight * $this->aspectRatio; - $this->log("Setting new width based on aspect ratio to {$this->newWidth}"); - - } elseif ($this->aspectRatio && is_null($this->newHeight)) { - $this->newHeight = $this->newWidth / $this->aspectRatio; - $this->log("Setting new height based on aspect ratio to {$this->newHeight}"); - } - - // Change width & height based on dpr - if ($this->dpr != 1) { - if (!is_null($this->newWidth)) { - $this->newWidth = round($this->newWidth * $this->dpr); - $this->log("Setting new width based on dpr={$this->dpr} - w={$this->newWidth}"); - } - if (!is_null($this->newHeight)) { - $this->newHeight = round($this->newHeight * $this->dpr); - $this->log("Setting new height based on dpr={$this->dpr} - h={$this->newHeight}"); - } - } - - // Check values to be within domain - 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'); - - $this->log("Init dimension (after) newWidth x newHeight is {$this->newWidth} x {$this->newHeight}."); + $this->imageResizer->setBaseWidthHeight($this->newWidth, $this->newHeight) + ->setBaseAspecRatio($this->aspectRatio) + ->setBaseDevicePixelRate($this->dpr) + ->prepareTargetDimensions(); return $this; } @@ -1024,6 +970,26 @@ class CImage */ public function calculateNewWidthAndHeight() { + $imres = $this->imageResizer; + $strategy = null; + + if ($this->keepRatio == true) { + $strategy = $imres::KEEP_RATIO; + } + if ($this->cropToFit == true) { + $strategy = $imres::CROP_TO_FIT; + } + if ($this->fillToFit == true) { + $strategy = $imres::FILL_TO_FIT; + } + + $imres->setResizeStrategy($strategy) + ->calculateTargetWidthAndHeight(); + + $this->newWidth = $imres->width(); + $this->newHeight = $imres->height(); + + /* // Crop, use cropped width and height as base for calulations $this->log("Calculate new width and height."); $this->log("Original width x height is {$this->width} x {$this->height}."); @@ -1145,18 +1111,12 @@ class CImage $this->newHeight = round(isset($this->newHeight) ? $this->newHeight : $this->crop['height']); } - // Fill to fit, ensure to set new width and height - /*if ($this->fillToFit) { - $this->log("FillToFit."); - $this->newWidth = round(isset($this->newWidth) ? $this->newWidth : $this->crop['width']); - $this->newHeight = round(isset($this->newHeight) ? $this->newHeight : $this->crop['height']); - }*/ - // No new height or width is set, use existing measures. $this->newWidth = round(isset($this->newWidth) ? $this->newWidth : $this->width); $this->newHeight = round(isset($this->newHeight) ? $this->newHeight : $this->height); $this->log("Calculated new width x height as {$this->newWidth} x {$this->newHeight}."); + */ return $this; } @@ -1667,6 +1627,7 @@ class CImage */ public function resize() { + $imgres = $this->imageResizer; $this->log("### Starting to Resize()"); $this->log("Upscale = '$this->upscale'"); @@ -1724,11 +1685,21 @@ class CImage $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight); imagecopy($imageResized, $this->image, $posX, $posY, $cropX, $cropY, $this->newWidth, $this->newHeight); } else { - $cropX = round(($this->cropWidth/2) - ($this->newWidth/2)); - $cropY = round(($this->cropHeight/2) - ($this->newHeight/2)); - $imgPreCrop = $this->CreateImageKeepTransparency($this->cropWidth, $this->cropHeight); + $cropX = $imgres->getCropX(); + $cropY = $imgres->getCropY(); + $cropWidth = $imgres->getCropWidth(); + $cropHeight = $imgres->getCropHeight(); + $this->log(" Crop from $cropX x $cropY by $cropWidth x $cropHeight."); + + + //$cropX = round(($this->cropWidth/2) - ($this->newWidth/2)); + //$cropY = round(($this->cropHeight/2) - ($this->newHeight/2)); + + //$imgPreCrop = $this->CreateImageKeepTransparency($this->cropWidth, $this->cropHeight); + $imgPreCrop = $this->CreateImageKeepTransparency($cropWidth, $cropHeight); $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight); - $this->imageCopyResampled($imgPreCrop, $this->image, 0, 0, 0, 0, $this->cropWidth, $this->cropHeight, $this->width, $this->height); + // $this->imageCopyResampled($imgPreCrop, $this->image, 0, 0, 0, 0, $this->cropWidth, $this->cropHeight, $this->width, $this->height); + $this->imageCopyResampled($imgPreCrop, $this->image, 0, 0, 0, 0, $cropWidth, $cropHeight, $this->width, $this->height); imagecopy($imageResized, $imgPreCrop, 0, 0, $cropX, $cropY, $this->newWidth, $this->newHeight); } @@ -2507,7 +2478,7 @@ class CImage /** - * Add HTTP header for putputting together with image. + * Add HTTP header for outputting together with image. * * @param string $type the header type such as "Cache-Control" * @param string $value the value to use diff --git a/CImageResizer.php b/CImageResizer.php index 19c4031..d8e0fdc 100644 --- a/CImageResizer.php +++ b/CImageResizer.php @@ -24,7 +24,7 @@ class CImageResizer /** - * Target image dimensions. + * Set as expected target image dimensions. */ private $targetWidth; //private $targetWidthOrig; // Save original value @@ -33,11 +33,21 @@ class CImageResizer + /** + * Which parts to crop from the source. + */ + private $cropX; + private $cropY; + private $cropWidth; + private $cropHeight; + + + /** * Change target height & width when different dpr, dpr 2 means double * image dimensions. */ - private $dpr; + private $dpr = null; @@ -48,6 +58,66 @@ class CImageResizer + /** + * Array with details on how to crop. + * Array contains xxxxxx + */ + public $crop; + public $cropOrig; // Save original value? + + + + /** + * Area to use for target image, crop out parts not in area. + * Array with top, right, bottom, left percentage values to crop out. + */ + private $area; + + + + /** + * Pixel offset in source image to decide which part of image is used. + * Array with top, right, bottom, left percentage values to crop out. + */ + private $offset; + + + + /** + * Resize strategy, image should keep its original ratio. + */ + const KEEP_RATIO = 1; + + + + /** + * Resize strategy, image should crop and fill area. + */ + const CROP_TO_FIT = 2; + + + + /** + * Resize strategy, image should fit in area and fill remains. + */ + const FILL_TO_FIT = 3; + + + + /** + * Resize strategy, image should stretch to fit in area. + */ + const STRETCH = 4; + + + + /** + * The currently selected resize strategy. + */ + private $resizeStrategy; + + + /** * Constructor, set log function to use for verbose logging or null * to disable logging. @@ -69,7 +139,7 @@ class CImageResizer public function log($str) { if ($this->log) { - echo $str . "\n"; + call_user_func($this->log, $str); } } @@ -89,7 +159,7 @@ class CImageResizer { $this->srcWidth = $width; $this->srcHeight = $height; - $this->log("Source image dimension: {$this->srcWidth} x {$this->srcHeight}."); + $this->log("# Source image dimension: {$this->srcWidth}x{$this->srcHeight}."); return $this; } @@ -97,57 +167,237 @@ class CImageResizer /** - * Set the basis to consider when calculating target dimensions. + * Get resize strategy as string. * - * @param numeric|null $width as requested target width - * @param numeric|null $height as requested density pixel rate - * @param float|null $aspectRatio as requested aspect ratio - * @param float $dpr as requested density pixel rate + * @return string + */ + public function getResizeStrategyAsString() + { + switch ($this->resizeStrategy) { + case self::KEEP_RATIO: + return "KEEP_RATIO"; + break; + + case self::CROP_TO_FIT: + return "CROP_TO_FIT"; + break; + + case self::FILL_TO_FIT: + return "FILL_TO_FIT"; + break; + + default: + return "UNKNOWN"; + } + } + + + + /** + * Set resize strategy as KEEP_RATIO, CROP_TO_FIT or FILL_TO_FIT. + * + * @param integer $strategy + * + * @return $this + */ + public function setResizeStrategy($strategy) + { + $this->resizeStrategy = $strategy; + $this->log("# Resize strategy is " . $this->getResizeStrategyAsString()); + + return $this; + } + + + + /** + * Set base for requested width and height. + * + * @param numeric|null $width as requested target width + * @param numeric|null $height as requested target height + * + * @throws Exception + * + * @return $this + */ + public function setBaseWidthHeight($width = null, $height = null) + { + $this->log("# Set base for width and height."); + + $this->targetWidth = $width; + $this->targetHeight = $height; + + // Width specified as % + if ($this->targetWidth[strlen($this->targetWidth)-1] == '%') { + $this->targetWidth = $this->srcWidth * substr($this->targetWidth, 0, -1) / 100; + $this->log(" Setting new width based on $width to {$this->targetWidth}."); + } + + // Height specified as % + if ($this->targetHeight[strlen($this->targetHeight)-1] == '%') { + $this->targetHeight = $this->srcHeight * substr($this->targetHeight, 0, -1) / 100; + $this->log(" Setting new height based on $height to {$this->targetHeight}."); + } + + if (!(is_null($this->targetWidth) || is_numeric($this->targetWidth))) { + throw new Exception('Width not numeric'); + } + + if (!(is_null($this->targetHeight) || is_numeric($this->targetHeight))) { + throw new Exception('Height not numeric'); + } + + $this->log(" Requested target dimension as: {$this->targetWidth}x{$this->targetHeight}."); + + return $this; + } + + + + /** + * Set base for requested aspect ratio. + * + * @param float|null $aspectRatio as requested aspect ratio + * + * @throws Exception + * + * @return $this + */ + public function setBaseAspecRatio($aspectRatio = null) + { + $this->log("# Set base for aspect ratio."); + + $this->aspectRatio = $aspectRatio; + + if (!(is_null($this->aspectRatio) || is_numeric($this->aspectRatio))) { + throw new Exception("Aspect ratio out of range"); + } + + $this->log(" Requested aspectRatio={$this->aspectRatio}."); + + return $this; + } + + + + /** + * Set base for requested device pixel ratio. + * + * @param float $dpr as requested density pixel rate * * @throws Exception * * @return $this */ - public function setBaseForCalculateTarget($width = null, $height = null, $aspectRatio = null, $dpr = 1) + public function setBaseDevicePixelRate($dpr = null) { - $this->targetWidth = $width; - $this->targetheight = $height; - $this->aspectRatio = $aspectRatio; - $this->dpr = $dpr; + $this->log("# Set base for device pixel rate."); - // Width specified as % - if ($this->targetWidth[strlen($this->targetWidth)-1] == '%') { - $this->targetWidth = $this->width * substr($this->targetWidth, 0, -1) / 100; - $this->log("Setting new width based on $width% to {$this->targetWidth}px."); + $this->dpr = $dpr; + + if (!(is_null($dpr) || (is_numeric($this->dpr) && $this->dpr > 0))) { + throw new Exception("Device pixel rate out of range"); } - // Height specified as % - if ($this->targetheight[strlen($this->targetheight)-1] == '%') { - $this->targetheight = $this->height * substr($this->targetheight, 0, -1) / 100; - $this->log("Setting new height based on $height% to {$this->targetheight}px."); + $this->log(" Requested dpr={$this->dpr}."); + + return $this; + } + + + + /** + * Calculate target width and height by considering the selected + * aspect ratio. + * + * @throws Exception + * + * @return $this + */ + public function prepareByConsiderAspectRatio() + { + $this->log(" Prepare by aspect ratio {$this->aspectRatio}."); + + if (is_null($this->aspectRatio)) { + return $this; } - // Width is valid - if (!(is_null($this->targetWidth) || is_numeric($this->targetWidth))) { - throw new Exception('Width not numeric'); + // Both null, use source as base for target + if (is_null($this->targetWidth) && is_null($this->targetHeight)) { + + $this->targetWidth = ($this->aspectRatio >= 1) + ? $this->srcWidth + : null; + + $this->targetHeight = ($this->aspectRatio >= 1) + ? null + : $this->srcHeight; + + $this->log(" Using source as base {$this->targetWidth}x{$this->targetHeight}"); + + } + + // Both or either set, calculate the other + if (isset($this->targetWidth) && isset($this->targetHeight)) { + + $this->targetWidth = ($this->aspectRatio >= 1) + ? $this->targetWidth + : $this->targetHeight * $this->aspectRatio; + + $this->targetHeight = ($this->aspectRatio >= 1) + ? $this->targetWidth / $this->aspectRatio + : $this->targetHeight; + + $this->log(" New target width height {$this->targetWidth}x{$this->targetHeight}"); + + } elseif (isset($this->targetWidth)) { + + $this->targetHeight = $this->targetWidth / $this->aspectRatio; + $this->log(" New target height x{$this->targetHeight}"); + + } elseif (isset($this->targetHeight)) { + + $this->targetWidth = $this->targetHeight * $this->aspectRatio; + $this->log(" New target width {$this->targetWidth}x"); + } - // Height is valid - if (!(is_null($this->targetheight) || is_numeric($this->targetheight))) { - throw new Exception('Height not numeric'); + return $this; + } + + + + /** + * Calculate target width and height by considering the selected + * dpr. + * + * @throws Exception + * + * @return $this + */ + public function prepareByConsiderDpr() + { + $this->log(" Prepare by dpr={$this->dpr}."); + + if (is_null($this->dpr)) { + return $this; } - // Aspect ratio is valid - if (!(is_null($this->aspectRatio) || is_numeric($this->aspectRatio))) { - throw new Exception('Aspect ratio out of range'); + // If both not set, use source as base + if (is_null($this->targetWidth) && is_null($this->targetHeight)) { + $this->targetWidth = $this->srcWidth; + $this->targetHeight = $this->srcHeight; } - // Device pixel ratio is valid - if (!(is_numeric($this->dpr) && $this->dpr > 0)) { - throw new Exception('Device pixel rate out of range'); + if (isset($this->targetWidth)) { + $this->targetWidth = $this->targetWidth * $this->dpr; + $this->log(" Update target width to {$this->targetWidth}."); } - $this->log("Requested target dimension as: {$this->targetWidth} x {$this->targetheight} aspectRatio={$this->aspectRatio}, dpr={$this->dpr}."); + if (isset($this->targetHeight)) { + $this->targetHeight = $this->targetHeight * $this->dpr; + $this->log(" Update target height to {$this->targetHeight}."); + } return $this; } @@ -159,52 +409,309 @@ class CImageResizer * After this method the $targetWidth and $targetHeight will have * the expected dimensions on the target image. * - * @param integer $width of source image. - * @param integer $height of source image. - * * @throws Exception * * @return $this */ public function prepareTargetDimensions() { - $this->log("Prepate target dimension before calculate: {$this->targetWidth} x {$this->targetheight}."); + $this->log("# Prepare target dimension (before): {$this->targetWidth}x{$this->targetHeight}."); - // width & height from aspect ratio - if ($this->aspectRatio && is_null($this->targetWidth) && is_null($this->targetheight)) { - if ($this->aspectRatio >= 1) { - $this->targetWidth = $this->width; - $this->targetheight = $this->width / $this->aspectRatio; - $this->log("Setting new width & height based on width & aspect ratio (>=1) to (w x h) {$this->targetWidth} x {$this->targetheight}"); + $this->prepareByConsiderAspectRatio() + ->prepareByConsiderDpr(); + + $this->log(" Prepare target dimension (after): {$this->targetWidth}x{$this->targetHeight}."); + return $this; + } + + + + /** + * Calculate new width and height of image. + * + * @return $this + */ + public function calculateTargetWidthAndHeight() + { + $this->log("# Calculate new width and height."); + $this->log(" Source size {$this->srcWidth}x{$this->srcHeight}."); + $this->log(" Target dimension (before) {$this->targetWidth}x{$this->targetHeight}."); +/* + // Set default values to crop area to be whole source image + $aspectRatio = $this->srcWidth / $this->srcHeight; + $this->cropX = 0; + $this->cropY = 0; + $this->cropWidth = $this->srcWidth; + $this->cropHeight = $this->srcHeight; + + // Get relations of original & target image + $width = $this->srcWidth; + $height = $this->srcHeight; +*/ + + // Set default values to crop area to be whole source image + $sw = $this->srcWidth; + $sh = $this->srcHeight; + $ar = $sw / $sh; + $tw = $this->targetWidth; + $th = $this->targetHeight; + $cx = 0; + $cy = 0; + $cw = $this->srcWidth; + $ch = $this->srcHeight; + + if (is_null($tw) && is_null($th)) { + + // No tw/th use sw/sh + $tw = $sw; + $th = $sh; + $this->log(" New tw x th {$tw}x{$th}"); + + } elseif (isset($tw) && is_null($th)) { + + // Keep aspect ratio, make th based on tw + $th = $tw / $ar; + $this->log(" New th x{$th}"); + + } elseif (is_null($tw) && isset($th)) { + + // Keep aspect ratio, make tw based on th + $tw = $th * $ar; + $this->log(" New tw {$tw}x"); + + } elseif (isset($tw) && isset($th)) { + + // Keep aspect ratio, make fit in imaginary box + if ($ar < 1) { + $tw = $th * $ar; + $this->log(" New tw {$tw}x"); } else { - $this->targetheight = $this->height; - $this->targetWidth = $this->height * $this->aspectRatio; - $this->log("Setting new width & height based on width & aspect ratio (<1) to (w x h) {$this->targetWidth} x {$this->targetheight}"); - } - - } elseif ($this->aspectRatio && is_null($this->targetWidth)) { - $this->targetWidth = $this->targetheight * $this->aspectRatio; - $this->log("Setting new width based on aspect ratio to {$this->targetWidth}"); - - } elseif ($this->aspectRatio && is_null($this->targetheight)) { - $this->targetheight = $this->targetWidth / $this->aspectRatio; - $this->log("Setting new height based on aspect ratio to {$this->targetheight}"); - } - - // Change width & height based on dpr - if ($this->dpr != 1) { - if (!is_null($this->targetWidth)) { - $this->targetWidth = round($this->targetWidth * $this->dpr); - $this->log("Setting new width based on dpr={$this->dpr} - w={$this->targetWidth}"); - } - if (!is_null($this->targetheight)) { - $this->targetheight = round($this->targetheight * $this->dpr); - $this->log("Setting new height based on dpr={$this->dpr} - h={$this->targetheight}"); + $th = $tw / $ar; + $this->log(" New th x{$th}"); } } - $this->log("prepare target dimension after calculate: {$this->targetWidth} x {$this->targetheight}."); +/* + if (isset($tw) && isset($th)) { + + // Both new width and height are set. + // Use targetWidth and targetHeight as max width/height, image + // should not be larger. + $ratioWidth = $width / $this->targetWidth; + $ratioHeight = $height / $this->targetHeight; + $ratio = ($ratioWidth > $ratioHeight) ? $ratioWidth : $ratioHeight; + $this->targetWidth = round($width / $ratio); + $this->targetHeight = round($height / $ratio); + $this->log(" New width and height was set."); + + } elseif (isset($this->targetWidth)) { + + // Use new width as max-width + $factor = (float)$this->targetWidth / (float)$width; + $this->targetHeight = round($factor * $height); + $this->log(" New height x$this->targetHeight."); + + } elseif (isset($this->targetHeight)) { + + // Use new height as max-hight + $factor = (float)$this->targetHeight / (float)$height; + $this->targetWidth = round($factor * $width); + $this->log(" New width {$this->targetWidth}x."); + + } + +*/ + + // No new height or width is set, use existing measures. + +/* + $this->targetWidth = isset($this->targetWidth) + ? $this->targetWidth + : $this->srcWidth; + $this->targetHeight = isset($this->targetHeight) + ? $this->targetHeight + : $this->srcHeight; +*/ + + $this->targetWidth = round($tw); + $this->targetHeight = round($th); + $this->cropX = round($cx); + $this->cropY = round($cy); + $this->cropWidth = round($cw); + $this->cropHeight = round($ch); + + $this->log(" Target dimension (after) {$this->targetWidth}x{$this->targetHeight}."); + $this->log(" Crop {$this->cropX}x{$this->cropY} by {$this->cropWidth}x{$this->cropHeight}."); + + + +/* + $ratioWidth = $this->srcWidth / $this->targetWidth; + $ratioHeight = $this->srcHeight / $this->targetHeight; + + + + if ($this->resizeStrategy === self::CROP_TO_FIT) { + + // Use targetWidth and targetHeight as defined + // width/height, image should fit the area. + $this->log(" Crop to fit."); + $ratio = ($ratioWidth < $ratioHeight) ? $ratioWidth : $ratioHeight; + $this->cropWidth = round($width / $ratio); + $this->cropHeight = round($height / $ratio); + $this->log(" Crop width, height, ratio: $this->cropWidth x $this->cropHeight ($ratio)."); + + } elseif ($this->resizeStrategy === self::FILL_TO_FIT) { + + // Use targetWidth and targetHeight as defined + // width/height, image should fit the area. + $this->log(" Fill to fit."); + $ratio = ($ratioWidth < $ratioHeight) ? $ratioHeight : $ratioWidth; + $this->fillWidth = round($width / $ratio); + $this->fillHeight = round($height / $ratio); + $this->log(" Fill width, height, ratio: $this->fillWidth x $this->fillHeight ($ratio)."); + } +*/ + + + // Check if there is an area to crop off + if (isset($this->area)) { + $this->offset['top'] = round($this->area['top'] / 100 * $this->srcHeight); + $this->offset['right'] = round($this->area['right'] / 100 * $this->srcWidth); + $this->offset['bottom'] = round($this->area['bottom'] / 100 * $this->srcHeight); + $this->offset['left'] = round($this->area['left'] / 100 * $this->srcWidth); + $this->offset['width'] = $this->srcWidth - $this->offset['left'] - $this->offset['right']; + $this->offset['height'] = $this->srcHeight - $this->offset['top'] - $this->offset['bottom']; + $this->srcWidth = $this->offset['width']; + $this->srcHeight = $this->offset['height']; + $this->log("The offset for the area to use is top {$this->area['top']}%, right {$this->area['right']}%, bottom {$this->area['bottom']}%, left {$this->area['left']}%."); + $this->log("The offset for the area to use is top {$this->offset['top']}px, right {$this->offset['right']}px, bottom {$this->offset['bottom']}px, left {$this->offset['left']}px, width {$this->offset['width']}px, height {$this->offset['height']}px."); + } + + + // Check if crop is set + if ($this->crop) { + $width = $this->crop['width'] = $this->crop['width'] <= 0 ? $this->srcWidth + $this->crop['width'] : $this->crop['width']; + $height = $this->crop['height'] = $this->crop['height'] <= 0 ? $this->srcHeight + $this->crop['height'] : $this->crop['height']; + + if ($this->crop['start_x'] == 'left') { + $this->crop['start_x'] = 0; + } elseif ($this->crop['start_x'] == 'right') { + $this->crop['start_x'] = $this->srcWidth - $width; + } elseif ($this->crop['start_x'] == 'center') { + $this->crop['start_x'] = round($this->srcWidth / 2) - round($width / 2); + } + + if ($this->crop['start_y'] == 'top') { + $this->crop['start_y'] = 0; + } elseif ($this->crop['start_y'] == 'bottom') { + $this->crop['start_y'] = $this->srcHeight - $height; + } elseif ($this->crop['start_y'] == 'center') { + $this->crop['start_y'] = round($this->srcHeight / 2) - round($height / 2); + } + + $this->log(" Crop area is width {$width}px, height {$height}px, start_x {$this->crop['start_x']}px, start_y {$this->crop['start_y']}px."); + } + +/* + // Calculate new width and height if keeping aspect-ratio. + if ($this->resizeStrategy === self::KEEP_RATIO) { + + $this->log(" Keep aspect ratio."); + + // Crop-to-fit and both new width and height are set. + if (($this->resizeStrategy === self::CROP_TO_FIT + || $this->resizeStrategy === self::FILL_TO_FIT) + && isset($this->targetWidth) + && isset($this->targetHeight) + ) { + + // Use targetWidth and targetHeight as width/height, image should + // fit in box. + $this->log(" Use targetWidth and targetHeight as width/height, image should fit in box."); + + } elseif (isset($this->targetWidth) && isset($this->targetHeight)) { + + // Both new width and height are set. + // Use targetWidth and targetHeight as max width/height, image + // should not be larger. + $ratioWidth = $width / $this->targetWidth; + $ratioHeight = $height / $this->targetHeight; + $ratio = ($ratioWidth > $ratioHeight) ? $ratioWidth : $ratioHeight; + $this->targetWidth = round($width / $ratio); + $this->targetHeight = round($height / $ratio); + $this->log(" New width and height was set."); + + } elseif (isset($this->targetWidth)) { + + // Use new width as max-width + $factor = (float)$this->targetWidth / (float)$width; + $this->targetHeight = round($factor * $height); + $this->log(" New height x$this->targetHeight."); + + } elseif (isset($this->targetHeight)) { + + // Use new height as max-hight + $factor = (float)$this->targetHeight / (float)$height; + $this->targetWidth = round($factor * $width); + $this->log(" New width {$this->targetWidth}x."); + + } + } + + +/* + // Get image dimensions for pre-resize image. + if ($this->resizeStrategy === self::CROP_TO_FIT + || $this->resizeStrategy === self::FILL_TO_FIT + ) { + + // Get relations of original & target image + $ratioWidth = $width / $this->targetWidth; + $ratioHeight = $height / $this->targetHeight; + + if ($this->resizeStrategy === self::CROP_TO_FIT) { + + // Use targetWidth and targetHeight as defined + // width/height, image should fit the area. + $this->log(" Crop to fit."); + $ratio = ($ratioWidth < $ratioHeight) ? $ratioWidth : $ratioHeight; + $this->cropWidth = round($width / $ratio); + $this->cropHeight = round($height / $ratio); + $this->log(" Crop width, height, ratio: $this->cropWidth x $this->cropHeight ($ratio)."); + + } elseif ($this->resizeStrategy === self::FILL_TO_FIT) { + + // Use targetWidth and targetHeight as defined + // width/height, image should fit the area. + $this->log(" Fill to fit."); + $ratio = ($ratioWidth < $ratioHeight) ? $ratioHeight : $ratioWidth; + $this->fillWidth = round($width / $ratio); + $this->fillHeight = round($height / $ratio); + $this->log(" Fill width, height, ratio: $this->fillWidth x $this->fillHeight ($ratio)."); + } + } +*/ + + + // Crop, ensure to set new width and height + if ($this->crop) { + $this->log(" Crop."); + $this->targetWidth = round(isset($this->targetWidth) + ? $this->targetWidth + : $this->crop['width']); + $this->targetHeight = round(isset($this->targetHeight) + ? $this->targetHeight + : $this->crop['height']); + } + + // Fill to fit, ensure to set new width and height + /*if ($this->fillToFit) { + $this->log("FillToFit."); + $this->targetWidth = round(isset($this->targetWidth) ? $this->targetWidth : $this->crop['width']); + $this->targetHeight = round(isset($this->targetHeight) ? $this->targetHeight : $this->crop['height']); + }*/ return $this; } @@ -214,11 +721,11 @@ class CImageResizer /** * Get target width. * - * @return $integer as target width + * @return integer as target width */ - public function width() + public function getTargetwidth() { - return $this->targetWidth; + return $this->targetWidth ? round($this->targetWidth) : null; } @@ -226,13 +733,90 @@ class CImageResizer /** * Get target height. * - * @return $integer as target height + * @return integer as target height */ - public function height() + public function getTargetheight() { - return $this->targetHeight; + return $this->targetHeight ? round($this->targetHeight) : null; } + /** + * Get crop position x. + * + * @return integer + */ + public function getCropX() + { + return $this->cropX; + /* + $cropX = 0; + + if ($this->cropWidth) { + $cropX = round(($this->cropWidth/2) - ($this->targetWidth/2)); + }; + + return $cropX;*/ + } + + + + /** + * Get crop position y. + * + * @return integer + */ + public function getCropY() + { + return $this->cropY; + /* + $cropY = 0; + + if ($this->cropHeight) { + $cropY = round(($this->cropHeight/2) - ($this->targetHeight/2)); + } + + return $cropY;*/ + } + + + + /** + * Get crop width. + * + * @return integer + */ + public function getCropWidth() + { + return $this->cropWidth; + /* + $cropWidth = $this->srcWidth; + + if ($this->cropWidth) { + $cropWidth = round($this->cropWidth); + } + + return $cropWidth;*/ + } + + + + /** + * Get crop height. + * + * @return integer + */ + public function getCropHeight() + { + return $this->cropHeight; + /* + $cropHeight = $this->srcHeight; + + if ($this->cropHeight) { + $cropHeight = round($this->cropHeight); + } + + return $cropHeight;*/ + } } diff --git a/phpunit.xml b/phpunit.xml index e406494..aadeeec 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -13,4 +13,14 @@ + + + . + + test + webroot + + + + diff --git a/test/CImageResizerTest.php b/test/CImageResizerTest.php index bbb8e80..3da2e0f 100644 --- a/test/CImageResizerTest.php +++ b/test/CImageResizerTest.php @@ -3,6 +3,11 @@ * A testclass * */ +function logger($str) +{ + echo "$str\n"; +} + class CImageResizerTest extends \PHPUnit_Framework_TestCase { /** @@ -13,10 +18,51 @@ class CImageResizerTest extends \PHPUnit_Framework_TestCase public function providerImages() { return array( + + // $strategy + // $srcWidth, $srcHeight, $targetWidth, $targetHeight, + // $aspectRatio, $dpr, + // $expectedWidth, $expectedHeight, + // $expectedWidth2, $expectedHeight2 + // Same as source, does not set target - array(100, 100, null, null, null, 1, null, null), - array(100, 150, null, null, null, 1, null, null), - array(150, 100, null, null, null, 1, null, null), + + // ===== Keep aspect ratio +/* + array(1, 100, 100, null, null, null, 1, null, null, 100, 100), + array(1, 100, 150, null, null, null, 1, null, null, 100, 150), + array(1, 150, 100, null, null, null, 1, null, null, 150, 100), + + // Width & Height as % + array(1, 100, 100, '200%', null, null, 1, 200, null, 200, 200), + array(1, 100, 100, null, '50%', null, 1, null, 50, 50, 50), + + // dpr + //array(1, 100, 100, null, null, null, 2, null, null, 100, 100), // do dpr? +/* + array(1, 100, 100, 100, null, null, 2, 200, null, 200, 200), + array(1, 100, 100, null, 100, null, 2, null, 200, 200, 200), + array(1, 100, 100, 100, 100, null, 2, 200, 200, 200, 200), +*/ + + // ===== Need crop to fit or fill to fit + // Aspect ratio +/* + array(2, 100, 100, null, null, 2, 1, 100, 50, 100, 50), + array(2, 100, 200, null, null, 4, 1, 100, 25, 100, 25), + array(2, 200, 100, null, null, 4, 1, 200, 50, 200, 50), + + // Aspect ratio inverted + array(2, 100, 100, null, null, 1/2, 1, 50, 100, 50, 100), + array(2, 100, 200, null, null, 1/4, 1, 50, 200, 50, 200), + array(2, 200, 100, null, null, 1/4, 1, 25, 100, 25, 100), + + // Aspect ratio & width + array(2, 100, 100, 200, null, 2, 1, 200, 100, 200, 100), + + // Aspect ratio & height + array(2, 100, 100, null, 200, 1/2, 1, 100, 200, 100, 200), +*/ ); } @@ -29,15 +75,98 @@ class CImageResizerTest extends \PHPUnit_Framework_TestCase * * @return void */ - public function testResize1($srcWidth, $srcHeight, $targetWidth, $targetHeigth, $aspectRatio, $dpr, $expectedWidth, $expectedHeight) + /* + public function testResize1($strategy, $srcWidth, $srcHeight, $targetWidth, $targetHeight, $aspectRatio, $dpr, $expectedWidth, $expectedHeight, $expectedWidth2, $expectedHeight2) { - $img = new CImageResizer(); + $img = new CImageResizer(/*'logger'*/ /*); + //$img = new CImageResizer('logger'); $img->setSource($srcWidth, $srcHeight) - ->setBaseForCalculateTarget($targetWidth, $targetHeigth, $aspectRatio, $dpr) + ->setResizeStrategy($strategy) + ->setBaseWidthHeight($targetWidth, $targetHeight) + ->setBaseAspecRatio($aspectRatio) + ->setBaseDevicePixelRate($dpr) ->prepareTargetDimensions(); - $this->assertEquals($expectedWidth, $img->width(), "Width not correct."); - $this->assertEquals($expectedHeight, $img->height(), "Heigth not correct."); + $this->assertEquals($expectedWidth, $img->getTargetWidth(), "Width not correct."); + $this->assertEquals($expectedHeight, $img->getTargetHeight(), "Height not correct."); + + $img->calculateTargetWidthAndHeight(); + $this->assertEquals($expectedWidth2, $img->getTargetWidth(), "Width not correct."); + $this->assertEquals($expectedHeight2, $img->getTargetHeight(), "Height not correct."); + + } + +*/ + + /** + * Provider + * + * @return array + */ + public function providerFaultImages() + { + return array( + array('xx', 100, null, 1), + array( 100, 'yy', null, 1), + array( 100, 100, 'zz', 1), + array( 100, 100, null, -1), + ); + } + + + + /** + * Test + * + * @dataProvider providerFaultImages + * + * @expectedException Exception + * + * @return void + */ + public function testResizeFaults($targetWidth, $targetHeight, $aspectRatio, $dpr) + { + $img = new CImageResizer(/*'logger'*/); + + $img->setBaseWidthHeight($targetWidth, $targetHeight) + ->setBaseAspecRatio($aspectRatio) + ->setBaseDevicePixelRate($dpr); + } + + + + /** + * Provider + * + * @return array + */ + public function providerResizeStrategy() + { + return array( + array(CImageResizer::KEEP_RATIO, "KEEP_RATIO"), + array(CImageResizer::CROP_TO_FIT, "CROP_TO_FIT"), + array(CImageResizer::FILL_TO_FIT, "FILL_TO_FIT"), + array(-1, "UNKNOWN"), + ); + } + + + + /** + * Test + * + * @dataProvider providerResizeStrategy + * + * @return void + */ + public function testResizeStrategy($strategy, $str) + { + $img = new CImageResizer(/*'logger'*/); + + $img->setResizeStrategy($strategy); + $res = $img->getResizeStrategyAsString(); + + $this->assertEquals($str, $res, "Strategy not matching."); } } diff --git a/webroot/img_config.php b/webroot/img_config.php index ae555e4..c05432a 100644 --- a/webroot/img_config.php +++ b/webroot/img_config.php @@ -140,6 +140,18 @@ return array( + /** + * Set skip-original to true to always process the image and use + * the cached version. Default is false and to use the original + * image when its no processing needed. + * + * Default value: + * skip_original: false + */ + //'skip_original' => false, + + + /** * A function (hook) can be called after img.php has processed all * configuration options and before processing the image using CImage.