CodeIgniter
An open source application development framework for PHP
This content is released under the MIT License (MIT)
Copyright (c) 2014-2019 British Columbia Institute of Technology Copyright (c) 2019 CodeIgniter Foundation
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
BaseHandler | Base image handling implementation |
<?php
/**
* CodeIgniter
*
* An open source application development framework for PHP
*
* This content is released under the MIT License (MIT)
*
* Copyright (c) 2014-2019 British Columbia Institute of Technology
* Copyright (c) 2019 CodeIgniter Foundation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @package CodeIgniter
* @author CodeIgniter Dev Team
* @copyright 2019 CodeIgniter Foundation
* @license https://opensource.org/licenses/MIT MIT License
* @link https://codeigniter.com
* @since Version 4.0.0
* @filesource
*/
namespace CodeIgniter\Images\Handlers;
use CodeIgniter\Images\Exceptions\ImageException;
use CodeIgniter\Images\Image;
use CodeIgniter\Images\ImageHandlerInterface;
/**
* Base image handling implementation
*/
abstract class BaseHandler implements ImageHandlerInterface
{
/**
* Configuration settings.
*
* @var \Config\Images
*/
protected $config;
/**
* The image/file instance
*
* @var \CodeIgniter\Images\Image
*/
protected $image = null;
/**
* Image width.
*
* @var integer
*/
protected $width = 0;
/**
* Image height.
*
* @var integer
*/
protected $height = 0;
/**
* File permission mask.
*
* @var type
*/
protected $filePermissions = 0644;
/**
* X-axis.
*
* @var integer
*/
protected $xAxis = 0;
/**
* Y-axis.
*
* @var integer
*/
protected $yAxis = 0;
/**
* Master dimensioning.
*
* @var string
*/
protected $masterDim = 'auto';
/**
* Default options for text watermarking.
*
* @var array
*/
protected $textDefaults = [
'fontPath' => null,
'fontSize' => 16,
'color' => 'ffffff',
'opacity' => 1.0,
'vAlign' => 'bottom',
'hAlign' => 'center',
'vOffset' => 0,
'hOffset' => 0,
'padding' => 0,
'withShadow' => false,
'shadowColor' => '000000',
'shadowOffset' => 3,
];
/**
* Temporary image used by the different engines.
*
* @var resource
*/
protected $resource;
//--------------------------------------------------------------------
/**
* Constructor.
*
* @param type $config
*/
public function __construct($config = null)
{
$this->config = $config;
}
//--------------------------------------------------------------------
/**
* Sets another image for this handler to work on.
* Keeps us from needing to continually instantiate the handler.
*
* @param string $path
*
* @return $this
*/
public function withFile(string $path)
{
// Clear out the old resource so that
// it doesn't try to use a previous image
$this->resource = null;
$this->image = new Image($path, true);
$this->image->getProperties(false);
$this->width = $this->image->origWidth;
$this->height = $this->image->origHeight;
return $this;
}
//--------------------------------------------------------------------
/**
* Make the image resource object if needed
*/
protected function ensureResource()
{
if ($this->resource === null)
{
$path = $this->image->getPathname();
// if valid image type, make corresponding image resource
switch ($this->image->imageType)
{
case IMAGETYPE_GIF:
$this->resource = imagecreatefromgif($path);
break;
case IMAGETYPE_JPEG:
$this->resource = imagecreatefromjpeg($path);
break;
case IMAGETYPE_PNG:
$this->resource = imagecreatefrompng($path);
break;
}
}
}
//--------------------------------------------------------------------
/**
* Returns the image instance.
*
* @return \CodeIgniter\Images\Image
*/
public function getFile()
{
return $this->image;
}
//--------------------------------------------------------------------
/**
* Returns the temporary image used during the image processing.
* Good for extending the system or doing things this library
* is not intended to do.
*
* @return resource
*/
public function getResource()
{
$this->ensureResource();
return $this->resource;
}
//--------------------------------------------------------------------
/**
* Resize the image
*
* @param integer $width
* @param integer $height
* @param boolean $maintainRatio If true, will get the closest match possible while keeping aspect ratio true.
* @param string $masterDim
*
* @return BaseHandler
*/
public function resize(int $width, int $height, bool $maintainRatio = false, string $masterDim = 'auto')
{
// If the target width/height match the source, then we have nothing to do here.
if ($this->image->origWidth === $width && $this->image->origHeight === $height)
{
return $this;
}
$this->width = $width;
$this->height = $height;
if ($maintainRatio)
{
$this->masterDim = $masterDim;
$this->reproportion();
}
return $this->_resize($maintainRatio);
}
//--------------------------------------------------------------------
/**
* Crops the image to the desired height and width. If one of the height/width values
* is not provided, that value will be set the appropriate value based on offsets and
* image dimensions.
*
* @param integer|null $width
* @param integer|null $height
* @param integer|null $x X-axis coord to start cropping from the left of image
* @param integer|null $y Y-axis coord to start cropping from the top of image
* @param boolean $maintainRatio
* @param string $masterDim
*
* @return mixed
*/
public function crop(int $width = null, int $height = null, int $x = null, int $y = null, bool $maintainRatio = false, string $masterDim = 'auto')
{
$this->width = $width;
$this->height = $height;
$this->xAxis = $x;
$this->yAxis = $y;
if ($maintainRatio)
{
$this->masterDim = $masterDim;
$this->reproportion();
}
$result = $this->_crop();
$this->xAxis = null;
$this->yAxis = null;
return $result;
}
//--------------------------------------------------------------------
/**
* Changes the stored image type to indicate the new file format to use when saving.
* Does not touch the actual resource.
*
* @param integer|null $imageType A PHP imageType constant, e.g. https://www.php.net/manual/en/function.image-type-to-mime-type.php
*
* @return $this
*/
public function convert(int $imageType)
{
$this->image->imageType = $imageType;
return $this;
}
//--------------------------------------------------------------------
/**
* Rotates the image on the current canvas.
*
* @param float $angle
*
* @return mixed
*/
public function rotate(float $angle)
{
// Allowed rotation values
$degs = [
90,
180,
270,
];
if ($angle === '' || ! in_array($angle, $degs))
{
throw ImageException::forMissingAngle();
}
// cast angle as an int, for our use
$angle = (int) $angle;
// Reassign the width and height
if ($angle === 90 || $angle === 270)
{
$temp = $this->height;
$this->width = $this->height;
$this->height = $temp;
}
// Call the Handler-specific version.
$this->_rotate($angle);
return $this;
}
//--------------------------------------------------------------------
/**
* Flattens transparencies, default white background
*
* @param integer $red
* @param integer $green
* @param integer $blue
*
* @return mixed
*/
public function flatten(int $red = 255, int $green = 255, int $blue = 255)
{
$this->width = $this->image->origWidth;
$this->height = $this->image->origHeight;
return $this->_flatten();
}
//--------------------------------------------------------------------
/**
* Handler-specific method to flattening an image's transparencies.
*
* @param integer $red
* @param integer $green
* @param integer $blue
*
* @return mixed
* @internal param int $angle
*/
protected abstract function _flatten(int $red = 255, int $green = 255, int $blue = 255);
//--------------------------------------------------------------------
/**
* Handler-specific method to handle rotating an image in 90 degree increments.
*
* @param integer $angle
*
* @return mixed
*/
protected abstract function _rotate(int $angle);
//--------------------------------------------------------------------
/**
* Flips an image either horizontally or vertically.
*
* @param string $dir Either 'vertical' or 'horizontal'
*
* @return $this
*/
public function flip(string $dir = 'vertical')
{
$dir = strtolower($dir);
if ($dir !== 'vertical' && $dir !== 'horizontal')
{
throw ImageException::forInvalidDirection($dir);
}
return $this->_flip($dir);
}
//--------------------------------------------------------------------
/**
* Handler-specific method to handle flipping an image along its
* horizontal or vertical axis.
*
* @param string $direction
*
* @return mixed
*/
protected abstract function _flip(string $direction);
//--------------------------------------------------------------------
/**
* Overlays a string of text over the image.
*
* Valid options:
*
* - color Text Color (hex number)
* - shadowColor Color of the shadow (hex number)
* - hAlign Horizontal alignment: left, center, right
* - vAlign Vertical alignment: top, middle, bottom
* - hOffset
* - vOffset
* - fontPath
* - fontSize
* - shadowOffset
*
* @param string $text
* @param array $options
*
* @return $this
*/
public function text(string $text, array $options = [])
{
$options = array_merge($this->textDefaults, $options);
$options['color'] = trim($options['color'], '# ');
$options['shadowColor'] = trim($options['shadowColor'], '# ');
$this->_text($text, $options);
return $this;
}
//--------------------------------------------------------------------
/**
* Handler-specific method for overlaying text on an image.
*
* @param string $text
* @param array $options
*/
protected abstract function _text(string $text, array $options = []);
//--------------------------------------------------------------------
/**
* Reads the EXIF information from the image and modifies the orientation
* so that displays correctly in the browser. This is especially an issue
* with images taken by smartphones who always store the image up-right,
* but set the orientation flag to display it correctly.
*
* @param boolean $silent If true, will ignore exceptions when PHP doesn't support EXIF.
*
* @return $this
*/
public function reorient(bool $silent = false)
{
$orientation = $this->getEXIF('Orientation', $silent);
switch ($orientation)
{
case 2:
return $this->flip('horizontal');
break;
case 3:
return $this->rotate(180);
break;
case 4:
return $this->rotate(180)
->flip('horizontal');
break;
case 5:
return $this->rotate(270)
->flip('horizontal');
break;
case 6:
return $this->rotate(270);
break;
case 7:
return $this->rotate(90)
->flip('horizontal');
break;
case 8:
return $this->rotate(90);
break;
default:
return $this;
}
}
//--------------------------------------------------------------------
/**
* Retrieve the EXIF information from the image, if possible. Returns
* an array of the information, or null if nothing can be found.
*
* EXIF data is only supported fr JPEG & TIFF formats.
*
* @param string|null $key If specified, will only return this piece of EXIF data.
*
* @param boolean $silent If true, will not throw our own exceptions.
*
* @return mixed
*/
public function getEXIF(string $key = null, bool $silent = false)
{
if (! function_exists('exif_read_data'))
{
if ($silent)
{
return null;
}
}
$exif = null; // default
switch ($this->image->imageType)
{
case IMAGETYPE_JPEG:
case IMAGETYPE_TIFF_II:
$exif = exif_read_data($this->image->getPathname());
if (! is_null($key) && is_array($exif))
{
$exif = $exif[$key] ?? false;
}
}
return $exif;
}
//--------------------------------------------------------------------
/**
* Combine cropping and resizing into a single command.
*
* Supported positions:
* - top-left
* - top
* - top-right
* - left
* - center
* - right
* - bottom-left
* - bottom
* - bottom-right
*
* @param integer $width
* @param integer $height
* @param string $position
*
* @return boolean
*/
public function fit(int $width, int $height = null, string $position = 'center')
{
$origWidth = $this->image->origWidth;
$origHeight = $this->image->origHeight;
list($cropWidth, $cropHeight) = $this->calcAspectRatio($width, $height, $origWidth, $origHeight);
if (is_null($height))
{
$height = ceil(($width / $cropWidth) * $cropHeight);
}
list($x, $y) = $this->calcCropCoords($cropWidth, $cropHeight, $origWidth, $origHeight, $position);
return $this->crop($cropWidth, $cropHeight, $x, $y)
->resize($width, $height);
}
//--------------------------------------------------------------------
/**
* Calculate image aspect ratio.
*
* @param $width
* @param null $height
* @param $origWidth
* @param $origHeight
*
* @return array
*/
protected function calcAspectRatio($width, $height = null, $origWidth, $origHeight): array
{
// If $height is null, then we have it easy.
// Calc based on full image size and be done.
if (is_null($height))
{
$height = ($width / $origWidth) * $origHeight;
return [
$width,
(int) $height,
];
}
$xRatio = $width / $origWidth;
$yRatio = $height / $origHeight;
if ($xRatio > $yRatio)
{
return [
$origWidth,
(int) ($origWidth * $height / $width),
];
}
return [
(int) ($origHeight * $width / $height),
$origHeight,
];
}
//--------------------------------------------------------------------
/**
* Based on the position, will determine the correct x/y coords to
* crop the desired portion from the image.
*
* @param $width
* @param $height
* @param $origWidth
* @param $origHeight
* @param $position
*
* @return array
*/
protected function calcCropCoords($width, $height, $origWidth, $origHeight, $position): array
{
$position = strtolower($position);
$x = $y = 0;
switch ($position)
{
case 'top-left':
$x = 0;
$y = 0;
break;
case 'top':
$x = floor(($origWidth - $width) / 2);
$y = 0;
break;
case 'top-right':
$x = $origWidth - $width;
$y = 0;
break;
case 'left':
$x = 0;
$y = floor(($origHeight - $height) / 2);
break;
case 'center':
$x = floor(($origWidth - $width) / 2);
$y = floor(($origHeight - $height) / 2);
break;
case 'right':
$x = ($origWidth - $width);
$y = floor(($origHeight - $height) / 2);
break;
case 'bottom-left':
$x = 0;
$y = $origHeight - $height;
break;
case 'bottom':
$x = floor(($origWidth - $width) / 2);
$y = $origHeight - $height;
break;
case 'bottom-right':
$x = ($origWidth - $width);
$y = $origHeight - $height;
break;
}
return [
$x,
$y,
];
}
//--------------------------------------------------------------------
/**
* Get the version of the image library in use.
*
* @return string
*/
public abstract function getVersion();
//--------------------------------------------------------------------
/**
* Saves any changes that have been made to file.
*
* Example:
* $image->resize(100, 200, true)
* ->save($target);
*
* @param string $target
* @param integer $quality
*
* @return mixed
*/
public abstract function save(string $target = null, int $quality = 90);
//--------------------------------------------------------------------
/**
* Does the driver-specific processing of the image.
*
* @param string $action
*
* @return mixed
*/
protected abstract function process(string $action);
//--------------------------------------------------------------------
/**
* Provide access to the Image class' methods if they don't exist
* on the handler itself.
*
* @param string $name
* @param array $args
*
* @return mixed
*/
public function __call(string $name, array $args = [])
{
if (method_exists($this->image, $name))
{
return $this->image->$name(...$args);
}
}
//--------------------------------------------------------------------
/**
* Re-proportion Image Width/Height
*
* When creating thumbs, the desired width/height
* can end up warping the image due to an incorrect
* ratio between the full-sized image and the thumb.
*
* This function lets us re-proportion the width/height
* if users choose to maintain the aspect ratio when resizing.
*
* @return void
*/
protected function reproportion()
{
if (($this->width === 0 && $this->height === 0) ||
$this->image->origWidth === 0 ||
$this->image->origHeight === 0 ||
( ! ctype_digit((string) $this->width) && ! ctype_digit((string) $this->height)) ||
! ctype_digit((string) $this->image->origWidth) ||
! ctype_digit((string) $this->image->origHeight)
)
{
return;
}
// Sanitize
$this->width = (int) $this->width;
$this->height = (int) $this->height;
if ($this->masterDim !== 'width' && $this->masterDim !== 'height')
{
if ($this->width > 0 && $this->height > 0)
{
$this->masterDim = ((($this->image->origHeight / $this->image->origWidth) - ($this->height / $this->width)) < 0) ? 'width' : 'height';
}
else
{
$this->masterDim = ($this->height === 0) ? 'width' : 'height';
}
}
elseif (($this->masterDim === 'width' && $this->width === 0) || ($this->masterDim === 'height' && $this->height === 0)
)
{
return;
}
if ($this->masterDim === 'width')
{
$this->height = (int) ceil($this->width * $this->image->origHeight / $this->image->origWidth);
}
else
{
$this->width = (int) ceil($this->image->origWidth * $this->height / $this->image->origHeight);
}
}
//--------------------------------------------------------------------
/**
* Return image width.
*
* accessor for testing; not part of interface
*
* @return integer
*/
public function getWidth()
{
return ($this->resource !== null) ? $this->_getWidth() : $this->width;
}
/**
* Return image height.
*
* accessor for testing; not part of interface
*
* @return type
*/
public function getHeight()
{
return ($this->resource !== null) ? $this->_getHeight() : $this->height;
}
}